1 /* $NetBSD: thread.c,v 1.8 2009/04/10 13:08:25 christos Exp $ */
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
33 * This module contains the threading and sorting routines.
38 #include <sys/cdefs.h>
40 __RCSID("$NetBSD: thread.c,v 1.8 2009/04/10 13:08:25 christos Exp $");
41 #endif /* not __lint__ */
57 struct message
*t_head
; /* head of the thread */
58 struct message
**t_msgtbl
; /* message array indexed by msgnum */
59 int t_msgCount
; /* count of messages in thread */
61 #define THREAD_INIT {NULL, NULL, 0}
64 #define S_STATE_INIT 0
65 #define S_EXPOSE 1 /* flag to expose the thread */
66 #define S_RESTRICT 2 /* flag to restrict to tagged messages */
67 #define S_IS_EXPOSE(a) ((a) & S_EXPOSE)
68 #define S_IS_RESTRICT(a) ((a) & S_RESTRICT)
70 /* XXX - this isn't really a thread */
71 static struct thread_s message_array
= THREAD_INIT
; /* the basic message array */
72 static struct thread_s current_thread
= THREAD_INIT
; /* the current thread */
74 static state_t state
= S_STATE_INIT
; /* the current state */
77 * A state hook used by the format module.
82 return !S_IS_EXPOSE(state
);
85 /************************************************************************
86 * Debugging stuff that should evaporate eventually.
90 show_msg(struct message
*mp
)
95 * Arg! '%p' doesn't like the '0' modifier.
97 (void)printf("%3d (%p):"
98 " flink=%p blink=%p clink=%p plink=%p"
99 " depth=%d flags=0x%03x\n",
101 mp
->m_flink
, mp
->m_blink
, mp
->m_clink
, mp
->m_plink
,
102 mp
->m_depth
, mp
->m_flag
);
108 show_thread(struct message
*mp
)
110 (void)printf("current_thread.t_head=%p\n", current_thread
.t_head
);
111 for (/*EMPTY*/; mp
; mp
= next_message(mp
))
117 thread_showcmd(void *v
)
121 (void)printf("current_thread.t_head=%p\n", current_thread
.t_head
);
122 for (ip
= v
; *ip
; ip
++)
123 show_msg(get_message(*ip
));
127 #endif /* THREAD_DEBUG */
129 /*************************************************************************
130 * tag/restrict routines
134 * Return TRUE iff all messages forward or below this one are tagged.
137 is_tagged_core(struct message
*mp
)
139 if (S_IS_EXPOSE(state
))
142 for (/*EMPTY*/; mp
; mp
= mp
->m_flink
)
143 if ((mp
->m_flag
& MTAGGED
) == 0 ||
144 is_tagged_core(mp
->m_clink
) == 0)
150 is_tagged(struct message
*mp
)
152 return mp
->m_flag
& MTAGGED
&& is_tagged_core(mp
->m_clink
);
155 /************************************************************************
156 * These are the core routines to access messages via the links used
157 * everywhere outside this module and fio.c.
161 has_parent(struct message
*mp
)
163 return mp
->m_plink
!= NULL
&&
164 mp
->m_plink
->m_clink
!= current_thread
.t_head
;
167 static struct message
*
168 next_message1(struct message
*mp
)
173 if (S_IS_EXPOSE(state
) == 0)
179 while (mp
->m_flink
== NULL
&& has_parent(mp
))
185 static struct message
*
186 prev_message1(struct message
*mp
)
191 if (S_IS_EXPOSE(state
) && mp
->m_blink
== NULL
&& has_parent(mp
))
197 PUBLIC
struct message
*
198 next_message(struct message
*mp
)
200 if (S_IS_RESTRICT(state
) == 0)
201 return next_message1(mp
);
203 while ((mp
= next_message1(mp
)) != NULL
&& is_tagged(mp
))
209 PUBLIC
struct message
*
210 prev_message(struct message
*mp
)
212 if (S_IS_RESTRICT(state
) == 0)
213 return prev_message1(mp
);
215 while ((mp
= prev_message1(mp
)) != NULL
&& is_tagged(mp
))
221 static struct message
*
222 first_message(struct message
*mp
)
224 if (S_IS_RESTRICT(state
) && is_tagged(mp
))
225 mp
= next_message(mp
);
229 PUBLIC
struct message
*
230 get_message(int msgnum
)
234 if (msgnum
< 1 || msgnum
> current_thread
.t_msgCount
)
236 mp
= current_thread
.t_msgtbl
[msgnum
- 1];
237 assert(mp
->m_index
== msgnum
);
242 get_msgnum(struct message
*mp
)
244 return mp
? mp
->m_index
: 0;
250 return current_thread
.t_msgCount
;
254 get_abs_msgCount(void)
256 return message_array
.t_msgCount
;
259 PUBLIC
struct message
*
260 get_abs_message(int msgnum
)
262 if (msgnum
< 1 || msgnum
> message_array
.t_msgCount
)
265 return &message_array
.t_head
[msgnum
- 1];
268 PUBLIC
struct message
*
269 next_abs_message(struct message
*mp
)
273 i
= (int)(mp
- message_array
.t_head
);
275 if (i
< 0 || i
+ 1 >= message_array
.t_msgCount
)
278 return &message_array
.t_head
[i
+ 1];
281 /************************************************************************/
283 * routines to handle the recursion of commands.
288 return S_IS_EXPOSE(state
) == 0 && value(ENAME_RECURSIVE_CMDS
) != NULL
;
292 thread_recursion_flist(struct message
*mp
, int (*fn
)(struct message
*, void *), void *args
)
295 for (/*EMPTY*/; mp
; mp
= mp
->m_flink
) {
296 if (S_IS_RESTRICT(state
) && is_tagged(mp
))
298 if ((retval
= fn(mp
, args
)) != 0 ||
299 (retval
= thread_recursion_flist(mp
->m_clink
, fn
, args
)) != 0)
307 thread_recursion(struct message
*mp
, int (*fn
)(struct message
*, void *), void *args
)
313 if ((retval
= fn(mp
, args
)) != 0)
316 if (do_recursion() &&
317 (retval
= thread_recursion_flist(mp
->m_clink
, fn
, args
)) != 0)
323 /************************************************************************
324 * A hook for sfmtfield() in format.c. It is the only place outside
325 * this module that the m_depth is known.
330 return current_thread
.t_head
? current_thread
.t_head
->m_depth
: 0;
333 /************************************************************************/
336 reindex_core(struct message
*mp
)
339 assert(mp
->m_blink
== NULL
);
342 for (mp
= first_message(mp
); mp
; mp
= mp
->m_flink
) {
343 assert(mp
->m_flink
== NULL
|| mp
== mp
->m_flink
->m_blink
);
344 assert(mp
->m_blink
== NULL
|| mp
== mp
->m_blink
->m_flink
);
346 assert(mp
->m_size
!= 0);
348 if (S_IS_RESTRICT(state
) == 0 || !is_tagged(mp
))
352 (void)reindex_core(mp
->m_clink
);
359 reindex(struct thread_s
*tp
)
366 if ((mp
= tp
->t_head
) == NULL
|| mp
->m_size
== 0)
369 assert(mp
->m_blink
== NULL
);
371 if (S_IS_EXPOSE(state
) == 0) {
373 * We special case this so that all the hidden
374 * sub-threads get indexed, not just the current one.
376 i
= reindex_core(tp
->t_head
);
380 for (mp
= first_message(tp
->t_head
); mp
; mp
= next_message(mp
))
384 assert(i
<= message_array
.t_msgCount
);
388 for (mp
= first_message(tp
->t_head
); mp
; mp
= next_message(mp
))
389 tp
->t_msgtbl
[i
++] = mp
;
393 redepth_core(struct message
*mp
, int depth
, struct message
*parent
)
395 assert(mp
->m_blink
== NULL
);
396 assert((parent
== NULL
&& depth
== 0) ||
397 (parent
!= NULL
&& depth
!= 0 && depth
== parent
->m_depth
+ 1));
399 for (/*EMPTY*/; mp
; mp
= mp
->m_flink
) {
400 assert(mp
->m_plink
== parent
);
401 assert(mp
->m_flink
== NULL
|| mp
== mp
->m_flink
->m_blink
);
402 assert(mp
->m_blink
== NULL
|| mp
== mp
->m_blink
->m_flink
);
403 assert(mp
->m_size
!= 0);
407 redepth_core(mp
->m_clink
, depth
+ 1, mp
);
412 redepth(struct thread_s
*thread
)
417 assert(thread
!= NULL
);
419 if ((mp
= thread
->t_head
) == NULL
|| mp
->m_size
== 0)
422 depth
= mp
->m_plink
? mp
->m_plink
->m_depth
+ 1 : 0;
424 #ifndef NDEBUG /* a sanity check if asserts are active */
429 for (tp
= mp
->m_plink
; tp
; tp
= tp
->m_plink
)
435 redepth_core(mp
, depth
, mp
->m_plink
);
438 /************************************************************************
439 * To be called after reallocating the main message list. It is here
440 * as it needs access to current_thread.t_head.
443 thread_fix_old_links(struct message
*nmessage
, struct message
*message
, int omsgCount
)
446 if (nmessage
== message
)
450 message_array
.t_head
= nmessage
; /* for assert check in thread_fix_new_links */
453 # define FIX_LINK(p) do {\
455 p = nmessage + (p - message);\
456 } while (/*CONSTCOND*/0)
458 FIX_LINK(current_thread
.t_head
);
459 for (i
= 0; i
< omsgCount
; i
++) {
460 FIX_LINK(nmessage
[i
].m_blink
);
461 FIX_LINK(nmessage
[i
].m_flink
);
462 FIX_LINK(nmessage
[i
].m_clink
);
463 FIX_LINK(nmessage
[i
].m_plink
);
465 for (i
= 0; i
< current_thread
.t_msgCount
; i
++)
466 FIX_LINK(current_thread
.t_msgtbl
[i
]);
472 thread_init(struct thread_s
*tp
, struct message
*mp
, int msgCount
)
476 if (tp
->t_msgtbl
== NULL
|| msgCount
> tp
->t_msgCount
) {
479 tp
->t_msgtbl
= ecalloc((size_t)msgCount
, sizeof(tp
->t_msgtbl
[0]));
482 tp
->t_msgCount
= msgCount
;
483 for (i
= 0; i
< msgCount
; i
++)
484 tp
->t_msgtbl
[i
] = &mp
[i
];
488 * To be called after reading in the new message structures.
489 * It is here as it needs access to current_thread.t_head.
492 thread_fix_new_links(struct message
*message
, int omsgCount
, int msgCount
)
495 struct message
*lastmp
;
497 /* This should only be called at the top level if omsgCount != 0! */
498 assert(omsgCount
== 0 || message
->m_plink
== NULL
);
499 assert(omsgCount
== 0 || message_array
.t_msgCount
== omsgCount
);
500 assert(message_array
.t_head
== message
);
502 message_array
.t_head
= message
;
503 message_array
.t_msgCount
= msgCount
;
504 assert(message_array
.t_msgtbl
== NULL
); /* never used */
509 * Find the end of the toplevel thread.
511 for (i
= 0; i
< omsgCount
; i
++) {
512 if (message_array
.t_head
[i
].m_depth
== 0 &&
513 message_array
.t_head
[i
].m_flink
== NULL
) {
514 lastmp
= &message_array
.t_head
[i
];
520 * lastmp better be unique!!!
522 for (i
++; i
< omsgCount
; i
++)
523 assert(message_array
.t_head
[i
].m_depth
!= 0 ||
524 message_array
.t_head
[i
].m_flink
!= NULL
);
525 assert(lastmp
!= NULL
);
529 * Link and index the new messages linearly at depth 0.
531 for (i
= omsgCount
; i
< msgCount
; i
++) {
532 message
[i
].m_index
= i
+ 1;
533 message
[i
].m_depth
= 0;
534 message
[i
].m_blink
= lastmp
;
535 message
[i
].m_flink
= NULL
;
536 message
[i
].m_clink
= NULL
;
537 message
[i
].m_plink
= NULL
;
539 lastmp
->m_flink
= &message
[i
];
540 lastmp
= &message
[i
];
544 * Make sure the current thread is setup correctly.
546 if (omsgCount
== 0) {
547 thread_init(¤t_thread
, message
, msgCount
);
551 * Make sure current_thread.t_msgtbl is always large
554 current_thread
.t_msgtbl
=
555 erealloc(current_thread
.t_msgtbl
,
556 msgCount
* sizeof(*current_thread
.t_msgtbl
));
558 assert(current_thread
.t_head
!= NULL
);
559 if (current_thread
.t_head
->m_depth
== 0)
560 reindex(¤t_thread
);
564 /************************************************************************/
566 * All state changes should go through here!!!
570 * NOTE: It is the caller's responsibility to ensure that the "dot"
571 * will be valid after a state change. For example, when changing
572 * from exposed to hidden threads, it is necessary to move the dot to
573 * the head of the thread or it will not be seen. Use thread_top()
574 * for this. Likewise, use first_visible_message() to locate the
575 * first visible message after a state change.
579 set_state(int and_bits
, int xor_bits
)
585 reindex(¤t_thread
);
586 redepth(¤t_thread
);
590 static struct message
*
591 first_visible_message(struct message
*mp
)
593 struct message
*oldmp
;
596 mp
= current_thread
.t_head
;
599 if ((S_IS_RESTRICT(state
) && is_tagged(mp
)) || mp
->m_flag
& MDELETED
)
600 mp
= next_message(mp
);
604 if ((S_IS_RESTRICT(state
) && is_tagged(mp
)) || mp
->m_flag
& MDELETED
)
605 mp
= prev_message(mp
);
608 mp
= current_thread
.t_head
;
614 restore_state(state_t new_state
)
617 reindex(¤t_thread
);
618 redepth(¤t_thread
);
619 dot
= first_visible_message(dot
);
622 static struct message
*
623 thread_top(struct message
*mp
)
625 while (mp
&& mp
->m_plink
) {
626 if (mp
->m_plink
->m_clink
== current_thread
.t_head
)
633 /************************************************************************/
635 * Possibly show the message list.
638 thread_announce(void *v
)
642 if (v
== NULL
) /* check this here to avoid it before each call */
646 (void)printf("No applicable messages\n");
649 vec
[0] = get_msgnum(dot
);
651 if (get_msgCount() > 0 && value(ENAME_NOHEADER
) == NULL
)
653 sawcom
= 0; /* so next will print the first message */
656 /************************************************************************/
659 * Flatten out the portion of the thread starting with the given
663 flattencmd_core(struct message
*mp
)
665 struct message
**marray
;
668 struct message
*nextmp
;
675 for (tp
= next_message(mp
); tp
&& tp
->m_depth
> mp
->m_depth
; tp
= next_message(tp
))
678 if (tp
&& tp
->m_depth
< mp
->m_depth
)
686 marray
= csalloc(mcount
, sizeof(*marray
));
688 for (i
= 0; i
< mcount
; i
++) {
690 tp
= next_message(tp
);
693 for (i
= 1; i
< mcount
; i
++) {
694 marray
[i
]->m_depth
= mp
->m_depth
;
695 marray
[i
]->m_plink
= mp
->m_plink
;
696 marray
[i
]->m_clink
= NULL
;
697 marray
[i
]->m_blink
= marray
[i
- 1];
698 marray
[i
- 1]->m_flink
= marray
[i
];
700 marray
[i
- 1]->m_flink
= nextmp
;
702 nextmp
->m_blink
= marray
[i
- 1];
706 * Flatten out all thread parts given in the message list, or the
707 * current thread, if none given.
717 if (*msgvec
) { /* a message was supplied */
718 for (ip
= msgvec
; *ip
; ip
++) {
720 mp
= get_message(*ip
);
725 else { /* no message given - flatten current thread */
727 for (mp
= first_message(current_thread
.t_head
);
728 mp
; mp
= next_message(mp
))
731 redepth(¤t_thread
);
737 /************************************************************************/
739 * The basic sort structure. For each message the index and key
740 * fields are set. The key field is used for the basic sort and the
741 * index is used to ensure that the order from the current thread is
742 * maintained when the key compare is equal.
745 struct message
*mp
; /* the message the following refer to */
747 char *str
; /* string sort key (typically a field or address) */
748 long lines
; /* a long sort key (typically a message line count) */
749 off_t size
; /* a size sort key (typically the message size) */
750 time_t time
; /* a time sort key (typically from date or headline) */
752 int index
; /* index from of the current thread before sorting */
753 /* XXX - do we really want index? It is always set to mp->m_index */
757 * This is the compare function obtained from the key_tbl[]. It is
758 * used by thread_array() to identify the end of the thread and by
759 * qsort_cmpfn() to do the basic sort.
763 int (*fn
)(const void *, const void *);
767 * The routine passed to qsort. Note that cmpfn must be set first!
770 qsort_cmpfn(const void *left
, const void *right
)
773 const struct key_sort_s
*lp
= left
;
774 const struct key_sort_s
*rp
= right
;
776 delta
= cmp
.fn(left
, right
);
777 return delta
? cmp
.inv
? - delta
: delta
: lp
->index
- rp
->index
;
781 link_array(struct key_sort_s
*marray
, size_t mcount
)
784 struct message
*lastmp
;
786 for (i
= 0; i
< mcount
; i
++) {
787 marray
[i
].mp
->m_index
= (int)i
+ 1;
788 marray
[i
].mp
->m_blink
= lastmp
;
789 marray
[i
].mp
->m_flink
= NULL
;
791 lastmp
->m_flink
= marray
[i
].mp
;
792 lastmp
= marray
[i
].mp
;
794 if (current_thread
.t_head
->m_plink
)
795 current_thread
.t_head
->m_plink
->m_clink
= marray
[0].mp
;
797 current_thread
.t_head
= marray
[0].mp
;
801 cut_array(struct key_sort_s
*marray
, size_t beg
, size_t end
)
806 assert(marray
[beg
].mp
->m_clink
== NULL
);
808 marray
[beg
].mp
->m_clink
= marray
[beg
+ 1].mp
;
809 marray
[beg
+ 1].mp
->m_blink
= NULL
;
811 marray
[beg
].mp
->m_flink
= marray
[end
].mp
;
813 marray
[end
].mp
->m_blink
= marray
[beg
].mp
;
815 marray
[end
- 1].mp
->m_flink
= NULL
;
817 for (i
= beg
+ 1; i
< end
; i
++)
818 marray
[i
].mp
->m_plink
= marray
[beg
].mp
;
823 thread_array(struct key_sort_s
*marray
, size_t mcount
, int cutit
)
825 struct message
*parent
;
827 parent
= marray
[0].mp
->m_plink
;
828 qsort(marray
, mcount
, sizeof(*marray
), qsort_cmpfn
);
829 link_array(marray
, mcount
);
834 * Flatten out the array.
836 for (i
= 0; i
< mcount
; i
++) {
837 marray
[i
].mp
->m_plink
= parent
;
838 marray
[i
].mp
->m_clink
= NULL
;
842 * Now chop it up. There is really only one level here.
845 for (j
= 1; j
< mcount
; j
++) {
846 if (cmp
.fn(&marray
[i
], &marray
[j
]) != 0) {
847 cut_array(marray
, i
, j
);
851 cut_array(marray
, i
, j
);
855 /************************************************************************/
857 * thread_on_reference() is the core reference threading routine. It
858 * is not a command itself by called by threadcmd().
862 adopt_child(struct message
*parent
, struct message
*child
)
865 * Unhook the child from its current location.
867 if (child
->m_blink
!= NULL
) {
868 child
->m_blink
->m_flink
= child
->m_flink
;
870 if (child
->m_flink
!= NULL
) {
871 child
->m_flink
->m_blink
= child
->m_blink
;
875 * Link the child to the parent.
877 if (parent
->m_clink
== NULL
) { /* parent has no child */
878 parent
->m_clink
= child
;
879 child
->m_blink
= NULL
;
881 else { /* add message to end of parent's child's flist */
883 for (t
= parent
->m_clink
; t
&& t
->m_flink
; t
= t
->m_flink
)
888 child
->m_flink
= NULL
;
889 child
->m_plink
= parent
;
893 * Get the parent ID for a message (if there is one).
895 * See RFC 2822, sec 3.6.4.
897 * Many mailers seem to screw up the In-Reply-To: and/or
898 * References: fields, generally by omitting one or both.
900 * We give preference to the "References" field. If it does
901 * not exist, try the "In-Reply-To" field. If neither exist,
902 * then the message is either not a reply or someone isn't
903 * adding the necessary fields, so skip it.
906 get_parent_id(struct message
*mp
)
910 if ((refs
= extract(hfield("references", mp
), 0)) != NULL
) {
912 while (refs
->n_flink
)
913 refs
= refs
->n_flink
;
915 id
= skin(refs
->n_name
);
920 return skin(hfield("in-reply-to", mp
));
924 * Thread on the "In-Reply-To" and "Reference" fields. This is the
925 * normal way to thread.
928 thread_on_reference(struct message
*mp
)
935 struct message
*parent
;
939 assert(mp
== current_thread
.t_head
);
941 oldstate
= set_state(~(S_RESTRICT
| S_EXPOSE
), S_EXPOSE
); /* restrict off, expose on */
943 mcount
= get_msgCount();
945 if (mcount
< 2) /* it's hard to thread so few messages! */
948 marray
= csalloc(mcount
+ 1, sizeof(*marray
));
951 * Load up the array (skin where necessary).
953 * With a 40K message file, most of the time is spent here,
954 * not in the search loop below.
956 for (i
= 0; i
< mcount
; i
++) {
958 marray
[i
].message_id
= skin(hfield("message-id", mp
));
959 marray
[i
].parent_id
= get_parent_id(mp
);
960 mp
= next_message(mp
);
964 * Save the old parent.
966 parent
= marray
[0].mp
->m_plink
;
971 marray
[0].mp
->m_clink
= NULL
;
972 for (i
= 1; i
< mcount
; i
++) {
973 marray
[i
].mp
->m_depth
= marray
[0].mp
->m_depth
;
974 marray
[i
].mp
->m_plink
= marray
[0].mp
->m_plink
;
975 marray
[i
].mp
->m_clink
= NULL
;
976 marray
[i
].mp
->m_blink
= marray
[i
- 1].mp
;
977 marray
[i
- 1].mp
->m_flink
= marray
[i
].mp
;
979 marray
[i
- 1].mp
->m_flink
= NULL
;
982 * Walk the array hooking up the replies with their parents.
984 for (i
= 0; i
< mcount
; i
++) {
985 struct message
*child
;
989 if ((parent_id
= marray
[i
].parent_id
) == NULL
)
992 child
= marray
[i
].mp
;
995 * Look for the parent message and link this one in
998 * XXX - This will not scale nicely, though it does
999 * not appear to be the dominant loop even with 40K
1000 * messages. If this becomes a problem, implement a
1003 for (j
= 0; j
< mcount
; j
++) {
1004 /* message_id will be NULL on mbox files */
1005 if (marray
[i
].message_id
== NULL
)
1008 if (equal(marray
[j
].message_id
, parent_id
)) {
1010 * The child is at the top level. If
1011 * it is being adopted and it was top
1012 * left (current_thread.t_head), then
1013 * its right sibling is the new top
1014 * left (current_thread.t_head).
1016 if (current_thread
.t_head
== child
) {
1017 current_thread
.t_head
= child
->m_flink
;
1018 assert(current_thread
.t_head
!= NULL
);
1020 adopt_child(marray
[j
].mp
, child
);
1027 parent
->m_clink
= current_thread
.t_head
;
1029 * If the old state is not exposed, reset the dot to the head
1030 * of the thread it lived in, so it will be in a valid spot
1031 * when things are re-hidden.
1033 if (!S_IS_EXPOSE(oldstate
))
1034 dot
= thread_top(dot
);
1036 restore_state(oldstate
);
1039 /************************************************************************/
1044 tag1(int *msgvec
, int and_bits
, int xor_bits
)
1048 for (ip
= msgvec
; *ip
!= 0; ip
++)
1049 (void)set_m_flag(*ip
, and_bits
, xor_bits
);
1051 reindex(¤t_thread
);
1052 /* thread_announce(v); */
1057 * Tag the current message dot or a message list.
1062 return tag1(v
, ~MTAGGED
, MTAGGED
);
1066 * Untag the current message dot or a message list.
1071 return tag1(v
, ~MTAGGED
, 0);
1075 * Invert all tags in the message list.
1080 return tag1(v
, ~0, MTAGGED
);
1084 * Tag all messages below the current dot or below a specified
1088 tagbelowcmd(void *v
)
1097 oldstate
= set_state(~(S_RESTRICT
| S_EXPOSE
), S_EXPOSE
); /* restrict off, expose on */
1098 mp
= get_message(*msgvec
);
1100 depth
= mp
->m_depth
;
1101 for (mp
= first_message(current_thread
.t_head
); mp
; mp
= next_message(mp
))
1102 if (mp
->m_depth
> depth
) {
1103 mp
->m_flag
|= MTAGGED
;
1108 restore_state(oldstate
);
1109 /* thread_announce(v); */
1114 * Do not display the tagged messages.
1117 hidetagscmd(void *v
)
1119 (void)set_state(~S_RESTRICT
, S_RESTRICT
); /* restrict on */
1120 dot
= first_visible_message(dot
);
1126 * Display the tagged messages.
1129 showtagscmd(void *v
)
1131 (void)set_state(~S_RESTRICT
, 0); /* restrict off */
1132 dot
= first_visible_message(dot
);
1137 /************************************************************************/
1139 * Basic threading commands.
1147 (void)set_state(~S_EXPOSE
, S_EXPOSE
); /* expose on */
1148 dot
= first_visible_message(dot
);
1159 dot
= thread_top(dot
);
1160 (void)set_state(~S_EXPOSE
, 0); /* expose off */
1161 dot
= first_visible_message(dot
);
1167 * Up one level in the thread tree. Go up multiple levels if given an
1178 str
= skip_WSP(str
);
1185 (void)printf("Sorry, argument must be > 0.\n");
1189 (void)printf("No applicable messages\n");
1192 if (dot
->m_plink
== NULL
) {
1193 (void)printf("top thread\n");
1197 while (upcnt
-- > 0) {
1198 struct message
*parent
;
1199 parent
= current_thread
.t_head
->m_plink
;
1200 if (parent
== NULL
) {
1201 (void)printf("top thread\n");
1206 assert(current_thread
.t_head
->m_depth
> 0);
1207 for (mp
= parent
; mp
&& mp
->m_blink
; mp
= mp
->m_blink
)
1209 current_thread
.t_head
= mp
;
1215 reindex(¤t_thread
);
1222 * Go down one level in the thread tree from the current dot or a
1223 * given message number if given.
1228 struct message
*child
;
1232 if ((mp
= get_message(*msgvec
)) == NULL
||
1233 (child
= mp
->m_clink
) == NULL
)
1234 (void)printf("no sub-thread\n");
1236 current_thread
.t_head
= child
;
1238 reindex(¤t_thread
);
1245 * Set the current thread level to the current dot or to the message
1254 if ((mp
= get_message(*msgvec
)) == NULL
)
1255 (void)printf("invalid message\n");
1257 for (/*EMPTY*/; mp
->m_blink
; mp
= mp
->m_blink
)
1259 current_thread
.t_head
= mp
;
1260 reindex(¤t_thread
);
1267 * Reverse the current thread order. If threaded, it only operates on
1271 reversecmd_core(struct thread_s
*tp
)
1273 struct message
*thread_start
;
1275 struct message
*lastmp
;
1276 struct message
*old_flink
;
1278 thread_start
= tp
->t_head
;
1280 assert(thread_start
->m_blink
== NULL
);
1283 for (mp
= thread_start
; mp
; mp
= old_flink
) {
1284 old_flink
= mp
->m_flink
;
1285 mp
->m_flink
= mp
->m_blink
;
1286 mp
->m_blink
= old_flink
;
1289 if (thread_start
->m_plink
)
1290 thread_start
->m_plink
->m_clink
= lastmp
;
1292 current_thread
.t_head
= lastmp
;
1299 reversecmd_core(¤t_thread
);
1306 * Get threading and sorting modifiers.
1308 #define MF_IGNCASE 1 /* ignore case when sorting */
1309 #define MF_REVERSE 2 /* reverse sort direction */
1310 #define MF_SKIN 4 /* "skin" the field to remove comments */
1312 get_modifiers(char **str
)
1318 for (p
= *str
; p
&& *p
; p
++) {
1321 modflags
|= MF_REVERSE
;
1324 modflags
|= MF_IGNCASE
;
1327 modflags
|= MF_SKIN
;
1341 /************************************************************************/
1343 * The key_sort_s compare routines.
1347 keystrcmp(const void *left
, const void *right
)
1349 const struct key_sort_s
*lp
= left
;
1350 const struct key_sort_s
*rp
= right
;
1355 if (rp
->key
.str
== NULL
&& lp
->key
.str
== NULL
)
1357 else if (rp
->key
.str
== NULL
)
1359 else if (lp
->key
.str
== NULL
)
1362 return strcmp(lp
->key
.str
, rp
->key
.str
);
1366 keystrcasecmp(const void *left
, const void *right
)
1368 const struct key_sort_s
*lp
= left
;
1369 const struct key_sort_s
*rp
= right
;
1371 if (rp
->key
.str
== NULL
&& lp
->key
.str
== NULL
)
1373 else if (rp
->key
.str
== NULL
)
1375 else if (lp
->key
.str
== NULL
)
1378 return strcasecmp(lp
->key
.str
, rp
->key
.str
);
1382 keylongcmp(const void *left
, const void *right
)
1384 const struct key_sort_s
*lp
= left
;
1385 const struct key_sort_s
*rp
= right
;
1387 if (lp
->key
.lines
> rp
->key
.lines
)
1390 if (lp
->key
.lines
< rp
->key
.lines
)
1397 keyoffcmp(const void *left
, const void *right
)
1399 const struct key_sort_s
*lp
= left
;
1400 const struct key_sort_s
*rp
= right
;
1402 if (lp
->key
.size
> rp
->key
.size
)
1405 if (lp
->key
.size
< rp
->key
.size
)
1412 keytimecmp(const void *left
, const void *right
)
1415 const struct key_sort_s
*lp
= left
;
1416 const struct key_sort_s
*rp
= right
;
1418 delta
= difftime(lp
->key
.time
, rp
->key
.time
);
1428 /************************************************************************
1429 * key_sort_s loading routines.
1432 field_load(struct key_sort_s
*marray
, size_t mcount
, struct message
*mp
,
1433 const char *key
, int skin_it
)
1436 for (i
= 0; i
< mcount
; i
++) {
1439 skin_it
? skin(hfield(key
, mp
)) : hfield(key
, mp
);
1440 marray
[i
].index
= mp
->m_index
;
1441 mp
= next_message(mp
);
1446 subj_load(struct key_sort_s
*marray
, size_t mcount
, struct message
*mp
,
1447 const char *key __unused
, int flags __unused
)
1454 for (i
= 0; i
< mcount
; i
++) {
1455 char *subj
= hfield(key
, mp
);
1456 while (strncasecmp(subj
, "Re:", 3) == 0)
1457 subj
= skip_WSP(subj
+ 3);
1459 marray
[i
].key
.str
= subj
;
1460 marray
[i
].index
= mp
->m_index
;
1461 mp
= next_message(mp
);
1467 lines_load(struct key_sort_s
*marray
, size_t mcount
, struct message
*mp
,
1468 const char *key __unused
, int flags
)
1479 use_hlines
= flags
== HLINES
;
1480 use_blines
= flags
== BLINES
;
1482 for (i
= 0; i
< mcount
; i
++) {
1484 marray
[i
].key
.lines
= use_hlines
? mp
->m_lines
- mp
->m_blines
:
1485 use_blines
? mp
->m_blines
: mp
->m_lines
;
1486 marray
[i
].index
= mp
->m_index
;
1487 mp
= next_message(mp
);
1492 size_load(struct key_sort_s
*marray
, size_t mcount
, struct message
*mp
,
1493 const char *key __unused
, int flags __unused
)
1500 for (i
= 0; i
< mcount
; i
++) {
1502 marray
[i
].key
.size
= mp
->m_size
;
1503 marray
[i
].index
= mp
->m_index
;
1504 mp
= next_message(mp
);
1508 static void __unused
1509 date_load(struct key_sort_s
*marray
, size_t mcount
, struct message
*mp
,
1510 const char *key __unused
, int flags
)
1514 int zero_hour_min_sec
;
1522 use_hl_date
= (flags
== RDAY
|| flags
== RDATE
);
1523 zero_hour_min_sec
= (flags
== RDAY
|| flags
== SDAY
);
1525 for (i
= 0; i
< mcount
; i
++) {
1527 (void)dateof(&tm
, mp
, use_hl_date
);
1528 if (zero_hour_min_sec
) {
1534 marray
[i
].key
.time
= mktime(&tm
);
1535 marray
[i
].index
= mp
->m_index
;
1536 mp
= next_message(mp
);
1541 from_load(struct key_sort_s
*marray
, size_t mcount
, struct message
*mp
,
1542 const char *key __unused
, int flags __unused
)
1549 for (i
= 0; i
< mcount
; i
++) {
1551 marray
[i
].key
.str
= nameof(mp
, 0);
1552 marray
[i
].index
= mp
->m_index
;
1553 mp
= next_message(mp
);
1557 /************************************************************************
1558 * The master table that controls all sorting and threading.
1560 static const struct key_tbl_s
{
1562 void (*loadfn
)(struct key_sort_s
*, size_t, struct message
*, const char *, int);
1564 int (*cmpfn
)(const void*, const void*);
1565 int (*casecmpfn
)(const void*, const void*);
1567 {"blines", lines_load
, BLINES
, keylongcmp
, keylongcmp
},
1568 {"hlines", lines_load
, HLINES
, keylongcmp
, keylongcmp
},
1569 {"tlines", lines_load
, TLINES
, keylongcmp
, keylongcmp
},
1570 {"size", size_load
, 0, keyoffcmp
, keyoffcmp
},
1571 {"sday", date_load
, SDAY
, keytimecmp
, keytimecmp
},
1572 {"rday", date_load
, RDAY
, keytimecmp
, keytimecmp
},
1573 {"sdate", date_load
, SDATE
, keytimecmp
, keytimecmp
},
1574 {"rdate", date_load
, RDATE
, keytimecmp
, keytimecmp
},
1575 {"from", from_load
, 0, keystrcasecmp
, keystrcasecmp
},
1576 {"subject", subj_load
, 0, keystrcmp
, keystrcasecmp
},
1577 {NULL
, field_load
, 0, keystrcmp
, keystrcasecmp
},
1582 * This is for use in complete.c to get the list of threading key
1583 * names without exposing the key_tbl[]. The first name is returned
1584 * if called with a pointer to a NULL pointer. Subsequent calls with
1585 * the same cookie give successive names. A NULL return indicates the
1589 thread_next_key_name(const void **cookie
)
1591 const struct key_tbl_s
*kp
;
1597 *cookie
= kp
->key
? &kp
[1] : NULL
;
1601 #endif /* USE_EDITLINE */
1603 static const struct key_tbl_s
*
1604 get_key(const char *key
)
1606 const struct key_tbl_s
*kp
;
1607 for (kp
= key_tbl
; kp
->key
!= NULL
; kp
++)
1608 if (strcmp(kp
->key
, key
) == 0)
1614 get_cmpfn(const struct key_tbl_s
*kp
, int ignorecase
)
1615 )(const void*, const void*)
1618 return kp
->casecmpfn
;
1624 thread_current_on(char *str
, int modflags
, int cutit
)
1626 const struct key_tbl_s
*kp
;
1627 struct key_sort_s
*marray
;
1631 oldstate
= set_state(~(S_RESTRICT
| S_EXPOSE
), cutit
? S_EXPOSE
: 0);
1634 mcount
= get_msgCount();
1635 marray
= csalloc(mcount
+ 1, sizeof(*marray
));
1636 kp
->loadfn(marray
, mcount
, current_thread
.t_head
, str
,
1637 kp
->flags
? kp
->flags
: modflags
& MF_SKIN
);
1638 cmp
.fn
= get_cmpfn(kp
, modflags
& MF_IGNCASE
);
1639 cmp
.inv
= modflags
& MF_REVERSE
;
1640 thread_array(marray
, mcount
, cutit
);
1642 if (!S_IS_EXPOSE(oldstate
))
1643 dot
= thread_top(dot
);
1644 restore_state(oldstate
);
1648 * The thread command. Thread the current thread on its references or
1649 * on a specified field.
1658 thread_on_reference(current_thread
.t_head
);
1661 modflags
= get_modifiers(&str
);
1662 thread_current_on(str
, modflags
, 1);
1669 * Remove all threading information, reverting to the startup state.
1672 unthreadcmd(void *v
)
1674 thread_fix_new_links(message_array
.t_head
, 0, message_array
.t_msgCount
);
1689 modflags
= get_modifiers(&str
);
1691 thread_current_on(str
, modflags
, 0);
1693 if (modflags
& MF_REVERSE
)
1694 reversecmd_core(¤t_thread
);
1696 (void)printf("sort on what?\n");
1706 * Delete duplicate messages (based on their "Message-Id" field).
1710 deldupscmd(void *v __unused
)
1716 oldstate
= set_state(~(S_RESTRICT
| S_EXPOSE
), S_EXPOSE
); /* restrict off, expose on */
1718 thread_current_on(__UNCONST("Message-Id"), 0, 1);
1719 reindex(¤t_thread
);
1720 redepth(¤t_thread
);
1721 depth
= current_thread
.t_head
->m_depth
;
1722 for (mp
= first_message(current_thread
.t_head
); mp
; mp
= next_message(mp
)) {
1723 if (mp
->m_depth
> depth
) {
1724 mp
->m_flag
&= ~(MPRESERVE
| MSAVED
| MBOX
);
1725 mp
->m_flag
|= MDELETED
| MTOUCH
;
1729 dot
= thread_top(dot
); /* do this irrespective of the oldstate */
1730 restore_state(oldstate
);
1731 /* thread_announce(v); */
1735 #endif /* THREAD_SUPPORT */