/* $Id: rthread.c,v 3.0 1992/12/14 00:14:13 davison Trn $ */ #include "EXTERN.h" #include "common.h" #include "intrp.h" #include "trn.h" #include "cache.h" #include "bits.h" #include "ng.h" #include "rcln.h" #include "search.h" #include "artstate.h" #include "rcstuff.h" #include "ngdata.h" #include "kfile.h" #include "head.h" #include "util.h" #include "hash.h" #include "nntp.h" #include "rt-mt.h" #include "rt-ov.h" #include "rt-page.h" #include "rt-process.h" #include "rt-select.h" #include "rt-wumpus.h" #include "INTERN.h" #include "rthread.h" HASHTABLE *msgid_hash = 0; bool try_ov = FALSE; bool try_mt = FALSE; void thread_init() { #ifdef USE_OV try_ov = ov_init(); #endif #ifdef USE_MT try_mt = mt_init(); #endif } /* Generate the thread data we need for this group. We must call ** thread_close() before calling this again. */ void thread_open() { if (!msgid_hash) msgid_hash = hashcreate(201, msgid_cmp); /*TODO: pick a better size */ if (ThreadedGroup) { /* Parse input and use msgid_hash for quick article lookups. */ /* If cached but not threaded articles exist, set up to thread them. */ if (first_subject) { first_cached = firstart; last_cached = firstart - 1; parsed_art = 0; } } if (sel_mode == SM_ARTICLE) set_selector(sel_mode, sel_artsort); else set_selector(sel_threadmode, sel_threadsort); #ifdef USE_MT if (try_mt && !first_subject) if (!mt_data()) return; #endif #ifdef USE_OV if (try_ov && first_cached > last_cached) if (thread_always) (void) ov_data(absfirst, lastart, FALSE); else if (firstart > lastart) { /* If no unread articles, see if ov. exists as quick as possible */ (void) ov_data(absfirst, absfirst, FALSE); first_cached = last_cached+1; } else (void) ov_data(firstart, lastart, FALSE); #endif #ifdef USE_NNTP if (!ov_opened) setmissingbits(); #endif if (last_cached > lastart) { toread[ng] += (ART_UNREAD)(last_cached-lastart); /* ensure getngsize() knows the new maximum */ ngmax[ng] = lastart = last_cached; } thread_grow(); /* thread any new articles not yet in the database */ added_articles = 0; sel_page_sp = 0; sel_page_app = 0; } /* Update the group's thread info. */ void thread_grow() { added_articles = lastart - last_cached; if (added_articles > 0 && thread_always) cache_range(last_cached + 1, lastart); count_subjects(CS_NORM); if (artptr_list) sort_articles(); else sort_subjects(); } static void kill_tmp_arts(data, extra) HASHDATUM *data; int extra; { register ARTICLE *ap = (ARTICLE*)data->dat_ptr; if (ap) free((char*)ap); } void thread_close() { curr_artp = artp = Nullart; init_tree(); /* free any tree lines */ if (msgid_hash) { hashwalk(msgid_hash, kill_tmp_arts, 0); hashdestroy(msgid_hash); msgid_hash = 0; } sel_page_sp = 0; sel_page_app = 0; sel_last_ap = 0; sel_last_sp = 0; selected_only = FALSE; #ifdef USE_OV ov_close(); #endif } ARTICLE * find_article(artnum) ART_NUM artnum; { if (artp && (artp->flags & AF_TMPMEM) == AF_TMPMEM && !artnum) return artp; if (artnum < absfirst || artnum > lastart) return (artp = Nullart); return (artp = article_ptr(artnum)); } void top_article() { art = lastart+1; artp = Nullart; inc_art(selected_only, FALSE); } ARTICLE * first_art(sp) register SUBJECT *sp; { register ARTICLE *ap = (ThreadedGroup? sp->thread : sp->articles); if (ap && (ap->flags & AF_MISSING)) ap = next_art(ap); return ap; } ARTICLE * last_art(sp) register SUBJECT *sp; { register ARTICLE *ap; if (!ThreadedGroup) { ap = sp->articles; while (ap->subj_next) ap = ap->subj_next; return ap; } ap = sp->thread; if (ap) { for (;;) { if (ap->sibling) ap = ap->sibling; else if (ap->child1) ap = ap->child1; else break; } if (ap->flags & AF_MISSING) ap = prev_art(ap); } return ap; } /* Bump art/artp to the next article, wrapping from thread to thread. ** If sel_flag is TRUE, only stops at selected articles. ** If rereading is FALSE, only stops at unread articles. */ void inc_art(sel_flag, rereading) bool_int sel_flag, rereading; { register ARTICLE *ap = artp; int subj_mask = (sel_mode == SM_THREAD? (SF_THREAD|SF_VISIT) : SF_VISIT); /* Use the explicit article-order if it exists */ if (artptr_list) { ARTICLE **limit = artptr_list + article_count; if (!ap) artptr = artptr_list-1; else if (!artptr || *artptr != ap) { for (artptr = artptr_list; artptr < limit; artptr++) { if (*artptr == ap) break; } } do { if (++artptr >= limit) break; ap = *artptr; } while ((!rereading && (ap->flags & AF_READ)) || (sel_flag && !(ap->flags & AF_SEL))); if (artptr < limit) { artp = *artptr; art = article_num(artp); } else { artp = Nullart; art = lastart+1; artptr = artptr_list; } return; } /* Use subject- or thread-order when possible */ if (ThreadedGroup || srchahead) { register SUBJECT *sp; bool try_this_group_again = !rereading; if (ap) sp = ap->subj; else for (sp = first_subject; sp && (sp->flags&subj_mask) != subj_mask;) sp = sp->next; if (!sp) goto num_inc; do { if (ap) ap = next_art(ap); else ap = first_art(sp); while (!ap) { if (try_this_group_again) try_this_group_again = FALSE; else { while ((sp = sp->next) != Nullsubj && (sp->flags & subj_mask) != subj_mask) ; if (!sp) break; } ap = first_art(sp); } } while (ap && ((!rereading && (ap->flags & AF_READ)) || (sel_flag && !(ap->flags & AF_SEL)))); if ((artp = ap) != Nullart) art = article_num(ap); else art = lastart+1; return; } /* Otherwise, just increment through the art numbers */ num_inc: if (!ap) { art = firstart-1; ap = article_ptr(art); } do { if (++art > lastart) { ap = Nullart; break; } ap++; } while ((!rereading && (ap->flags & AF_READ)) || (sel_flag && !(ap->flags & AF_SEL)) || (ap->flags & AF_MISSING)); artp = ap; } /* Bump art/artp to the previous article, wrapping from thread to thread. ** If sel_flag is TRUE, only stops at selected articles. ** If rereading is FALSE, only stops at unread articles. */ void dec_art(sel_flag, rereading) bool_int sel_flag, rereading; { register ARTICLE *ap = artp; int subj_mask = (sel_mode == SM_THREAD? (SF_THREAD|SF_VISIT) : SF_VISIT); /* Use the explicit article-order if it exists */ if (artptr_list) { ARTICLE **limit = artptr_list + article_count; if (!ap) artptr = limit; else if (!artptr || *artptr != ap) { for (artptr = artptr_list; artptr < limit; artptr++) { if (*artptr == ap) break; } } do { if (artptr == artptr_list) break; ap = *--artptr; } while ((!rereading && (ap->flags & AF_READ)) || (sel_flag && !(ap->flags & AF_SEL))); artp = *artptr; art = article_num(artp); return; } /* Use subject- or thread-order when possible */ if (ThreadedGroup || srchahead) { register SUBJECT *sp; if (ap) sp = ap->subj; else for (sp = last_subject; sp && (sp->flags&subj_mask) != subj_mask;) sp = sp->prev; if (!sp) goto num_dec; do { if (ap) ap = prev_art(ap); else ap = last_art(sp); while (!ap) { while ((sp = sp->prev) != Nullsubj && (sp->flags & subj_mask) != subj_mask) ; if (!sp) break; ap = last_art(sp); } } while (ap && ((!rereading && (ap->flags & AF_READ)) || (sel_flag && !(ap->flags & AF_SEL)))); if ((artp = ap) != Nullart) art = article_num(ap); else art = absfirst-1; return; } /* Otherwise, just decrement through the art numbers */ num_dec: ap = article_ptr(art); do { if (--art < absfirst) { ap = Nullart; break; } ap--; } while ((!rereading && (ap->flags & AF_READ)) || (sel_flag && !(ap->flags & AF_SEL)) || (ap->flags & AF_MISSING)); artp = ap; } /* Bump the param to the next article in depth-first order. */ ARTICLE * bump_art(ap) register ARTICLE *ap; { if (ap->child1) return ap->child1; while (!ap->sibling) { if (!(ap = ap->parent)) return Nullart; } return ap->sibling; } /* Bump the param to the next REAL article. Uses subject order in a ** non-threaded group; honors the breadth_first flag in a threaded one. */ ARTICLE * next_art(ap) register ARTICLE *ap; { try_again: if (!ThreadedGroup) { ap = ap->subj_next; goto done; } if (breadth_first) { if (ap->sibling) { ap = ap->sibling; goto done; } if (ap->parent) ap = ap->parent->child1; else ap = ap->subj->thread; } do { if (ap->child1) { ap = ap->child1; goto done; } while (!ap->sibling) { if (!(ap = ap->parent)) return Nullart; } ap = ap->sibling; } while (breadth_first); done: if (ap && (ap->flags & AF_MISSING)) goto try_again; return ap; } /* Bump the param to the previous REAL article. Uses subject order in a ** non-threaded group. */ ARTICLE * prev_art(ap) register ARTICLE *ap; { register ARTICLE *initial_ap; try_again: initial_ap = ap; if (!ThreadedGroup) { if ((ap = ap->subj->articles) == initial_ap) ap = Nullart; else while (ap->subj_next != initial_ap) ap = ap->subj_next; goto done; } ap = (ap->parent ? ap->parent->child1 : ap->subj->thread); if (ap == initial_ap) { ap = ap->parent; goto done; } while (ap->sibling != initial_ap) ap = ap->sibling; while (ap->child1) { ap = ap->child1; while (ap->sibling) ap = ap->sibling; } done: if (ap && (ap->flags & AF_MISSING)) goto try_again; return ap; } /* Find the next art/artp with the same subject as this one. Returns ** FALSE if no such article exists. */ bool next_art_with_subj() { register ARTICLE *ap = artp; register SUBJECT *sp; bool try_this_subj_again = TRUE; if (!ap) return FALSE; sp = ap->subj; do { ap = ap->subj_next; if (!ap) { if (try_this_subj_again) { ap = sp->articles; try_this_subj_again = FALSE; } else { if (!art) art = firstart; return FALSE; } } } while ((ap->flags & (AF_READ|AF_MISSING)) || (selected_only && !(ap->flags & AF_SEL))); artp = ap; art = article_num(ap); #ifdef ARTSEARCH srchahead = -1; #endif return TRUE; } /* Find the previous art/artp with the same subject as this one. Returns ** FALSE if no such article exists. */ bool prev_art_with_subj() { register ARTICLE *ap = artp, *ap2; register SUBJECT *sp; bool try_this_subj_again = TRUE; if (!ap) return FALSE; sp = ap->subj; do { ap2 = ap->subj->articles; if (ap2 == ap) ap = Nullart; else { while (ap2 && ap2->subj_next != ap) ap2 = ap2->subj_next; ap = ap2; } if (!ap) { if (try_this_subj_again) { ap = sp->articles; while (ap->subj_next) ap = ap->subj_next; try_this_subj_again = FALSE; } else { if (!art) art = lastart; return FALSE; } } } while ((ap->flags & (AF_READ|AF_MISSING)) || (selected_only && !(ap->flags & AF_SEL))); artp = ap; art = article_num(ap); return TRUE; } /* Select a single article. */ void select_article(ap, sel_flags) register ARTICLE *ap; int sel_flags; { int desired_flags = (sel_rereading? AF_READ : 0); #ifdef VERBOSE bool echo; if (sel_flags & AF_ECHO) { echo = TRUE; sel_flags &= ~AF_ECHO; } else echo = FALSE; #else sel_flags &= ~AF_ECHO; #endif if (sel_flags & (AF_AUTOSELECT|AF_AUTOSELECTALL)) save_ids = TRUE; if ((ap->flags & (AF_MISSING|AF_READ)) == desired_flags) { if (!(ap->flags & sel_mask)) { selected_count++; #ifdef VERBOSE if (echo) { IF(verbose) fputs("\tSelected",stdout); } #endif } ap->flags = (ap->flags & ~AF_DEL) | sel_mask | sel_flags; } else ap->flags |= sel_flags; if (!(ap->subj->flags & sel_mask)) selected_subj_cnt++; ap->subj->flags = (ap->subj->flags&~SF_DEL)|sel_mask|sel_flags|SF_VISIT; if (sel_mode == SM_THREAD) { if ((ap = ap->subj->thread) != NULL) ap->subj->flags |= SF_VISIT; } selected_only = (selected_only || selected_count != 0); } /* Select all the articles in a subject. */ void select_subject(subj, sel_flags) SUBJECT *subj; int sel_flags; { register ARTICLE *ap; int desired_flags = (sel_rereading? AF_READ : 0); int old_count = selected_count; if (sel_flags & (AF_AUTOSELECT|AF_AUTOSELECTALL)) { save_ids = TRUE; if (sel_flags & AF_AUTOSELECTALL) subj->flags |= SF_AUTOSELECT; } for (ap = subj->articles; ap; ap = ap->subj_next) { if ((ap->flags & (AF_MISSING|AF_READ|sel_mask)) == desired_flags) { ap->flags |= sel_mask | sel_flags; selected_count++; } else ap->flags |= sel_flags; } if (selected_count > old_count) { if (!(subj->flags & sel_mask)) selected_subj_cnt++; subj->flags = (subj->flags & ~SF_DEL) | sel_mask | SF_VISIT | SF_WASSELECTED; if (sel_mode == SM_THREAD) { if ((ap = subj->thread) != NULL) ap->subj->flags |= SF_VISIT; } selected_only = TRUE; } else subj->flags |= SF_WASSELECTED; } /* Select all the articles in a thread. */ void select_thread(thread, sel_flags) register ARTICLE *thread; int sel_flags; { register SUBJECT *sp; sp = thread->subj; do { select_subject(sp, sel_flags); sp = sp->thread_link; } while (sp != thread->subj); } /* Select the subthread attached to this article. */ void select_subthread(ap, sel_flags) register ARTICLE *ap; int sel_flags; { register ARTICLE *limit; SUBJECT *subj; int desired_flags = (sel_rereading? AF_READ : 0); int old_count = selected_count; if (!ap) return; subj = ap->subj; for (limit = ap; limit; limit = limit->parent) { if (limit->sibling) { limit = limit->sibling; break; } } if (sel_flags & (AF_AUTOSELECT|AF_AUTOSELECTALL)) save_ids = TRUE; for (; ap != limit; ap = bump_art(ap)) { if ((ap->flags & (AF_MISSING|AF_READ|sel_mask)) == desired_flags) { ap->flags |= sel_mask | sel_flags; selected_count++; } else ap->flags |= sel_flags; } if (selected_count > old_count) { if (!(subj->flags & sel_mask)) selected_subj_cnt++; subj->flags = (subj->flags & ~SF_DEL) | sel_mask | SF_VISIT; if (sel_mode == SM_THREAD) { if ((ap = subj->thread) != NULL) ap->subj->flags |= SF_VISIT; } selected_only = TRUE; } } /* Deselect a single article. */ void deselect_article(ap) register ARTICLE *ap; { if (ap->flags & sel_mask) { ap->flags &= ~sel_mask; if (!selected_count--) selected_count = 0; #ifdef VERBOSE if (mode != 't') { IF(verbose) fputs("\tDeselected",stdout); } #endif } if (sel_rereading && sel_mode == SM_ARTICLE) ap->flags |= AF_DEL; } /* Deselect all the articles in a subject. */ void deselect_subject(subj) SUBJECT *subj; { register ARTICLE *ap; for (ap = subj->articles; ap; ap = ap->subj_next) { if (ap->flags & sel_mask) { ap->flags &= ~sel_mask; if (!selected_count--) selected_count = 0; } } if (subj->flags & sel_mask) { subj->flags &= ~sel_mask; selected_subj_cnt--; } subj->flags &= ~(SF_VISIT | SF_WASSELECTED); if (sel_rereading) subj->flags |= SF_DEL; else subj->flags &= ~SF_DEL; } /* Deselect all the articles in a thread. */ void deselect_thread(thread) register ARTICLE *thread; { register SUBJECT *sp; sp = thread->subj; do { deselect_subject(sp); sp = sp->thread_link; } while (sp != thread->subj); } /* Deselect everything. */ void deselect_all() { register SUBJECT *sp; for (sp = first_subject; sp; sp = sp->next) deselect_subject(sp); selected_count = selected_subj_cnt = 0; sel_page_sp = 0; sel_page_app = 0; sel_last_ap = 0; sel_last_sp = 0; selected_only = FALSE; } /* Kill all unread articles attached to the given subject. */ void kill_subject(subj, kill_flags) SUBJECT *subj; int kill_flags; { register ARTICLE *ap; register int killmask = ((kill_flags&KF_ALL)? AF_READ:(AF_READ|sel_mask)); if (kill_flags & KF_KILLFILE) { save_ids = TRUE; kill_flags = AF_AUTOKILLALL; } else kill_flags = 0; for (ap = subj->articles; ap; ap = ap->subj_next) { if (!(ap->flags & killmask)) set_read(ap); ap->flags |= kill_flags; } subj->flags &= ~(SF_VISIT | SF_WASSELECTED); } /* Kill all unread articles attached to the given thread. */ void kill_thread(thread, kill_flags) register ARTICLE *thread; int kill_flags; { register SUBJECT *sp; sp = thread->subj; do { kill_subject(sp, kill_flags); sp = sp->thread_link; } while (sp != thread->subj); } /* Kill the subthread attached to this article. */ void kill_subthread(ap, kill_flags) register ARTICLE *ap; int kill_flags; { register ARTICLE *limit; if (!ap) return; for (limit = ap; limit; limit = limit->parent) { if (limit->sibling) { limit = limit->sibling; break; } } if (kill_flags & KF_KILLFILE) { save_ids = TRUE; kill_flags = AF_AUTOKILL; } else kill_flags = 0; for (; ap != limit; ap = bump_art(ap)) { if (!(ap->flags & (AF_READ|AF_MISSING))) set_read(ap); ap->flags |= kill_flags; } } /* Unkill all the articles attached to the given subject. */ void unkill_subject(subj) SUBJECT *subj; { register ARTICLE *ap; for (ap = subj->articles; ap; ap = ap->subj_next) { if (sel_rereading) { if ((ap->flags & (AF_DELSEL|AF_MISSING)) == AF_DELSEL) { if (ap->flags & AF_READ) toread[ng]++; ap->flags = (ap->flags & ~(AF_DELSEL|AF_READ)) | AF_SEL; } else ap->flags &= ~(AF_DEL|AF_DELSEL); } else { if ((ap->flags & (AF_READ|AF_MISSING)) == AF_READ) onemore(ap); if (selected_only && !(ap->flags & (AF_SEL|AF_READ))) { ap->flags = (ap->flags & ~AF_DEL) | AF_SEL; selected_count++; } } } if (!sel_rereading && selected_only && !(subj->flags & SF_SEL)) { subj->flags |= SF_SEL | SF_VISIT | SF_WASSELECTED; if (sel_mode == SM_THREAD) { if ((ap = subj->thread) != NULL) ap->subj->flags |= SF_VISIT; } selected_subj_cnt++; } subj->flags &= ~(SF_DEL|SF_DELSEL); } /* Unkill all the articles attached to the given thread. */ void unkill_thread(thread) register ARTICLE *thread; { register SUBJECT *sp; sp = thread->subj; do { unkill_subject(sp); sp = sp->thread_link; } while (sp != thread->subj); } /* Unkill the subthread attached to this article. */ void unkill_subthread(ap) register ARTICLE *ap; { register ARTICLE *limit; register SUBJECT *sp; if (!ap) return; for (limit = ap; limit; limit = limit->parent) { if (limit->sibling) { limit = limit->sibling; break; } } sp = ap->subj; for (; ap != limit; ap = bump_art(ap)) { if ((ap->flags & (AF_READ|AF_MISSING)) == AF_READ) onemore(ap); if (selected_only && !(ap->flags & AF_SEL)) { ap->flags |= AF_SEL; selected_count++; } } if (!(sp->flags & sel_mask)) selected_subj_cnt++; sp->flags = (sp->flags & ~SF_DEL) | SF_SEL | SF_VISIT; if (sel_mode == SM_THREAD) { if ((ap = sp->thread) != NULL) ap->subj->flags |= SF_VISIT; } selected_only = (selected_only || selected_count != 0); } /* Kill all unread articles attached to the given subject. */ void clear_subject(subj) SUBJECT *subj; { register ARTICLE *ap; for (ap = subj->articles; ap; ap = ap->subj_next) { ap->flags &= ~AF_AUTOFLAGS; } subj->flags &= ~(SF_WASSELECTED | SF_AUTOSELECT); } /* Kill all unread articles attached to the given thread. */ void clear_thread(thread) register ARTICLE *thread; { register SUBJECT *sp; sp = thread->subj; do { clear_subject(sp); sp = sp->thread_link; } while (sp != thread->subj); } /* Kill the subthread attached to this article. */ void clear_subthread(ap) register ARTICLE *ap; { register ARTICLE *limit; if (!ap) return; for (limit = ap; limit; limit = limit->parent) { if (limit->sibling) { limit = limit->sibling; break; } } for (; ap != limit; ap = bump_art(ap)) { ap->flags &= ~AF_AUTOFLAGS; } } ARTICLE * subj_art(sp) SUBJECT *sp; { register ARTICLE *ap = Nullart; int art_mask = (selected_only? AF_SEL : 0); bool TG_save = ThreadedGroup; ThreadedGroup = (sel_mode == SM_THREAD); ap = first_art(sp); while (ap && (ap->flags & (art_mask|AF_READ)) != art_mask) ap = next_art(ap); if (!ap) { reread = TRUE; ap = first_art(sp); if (art_mask) { while (ap && !(ap->flags & AF_SEL)) ap = next_art(ap); if (!ap) ap = first_art(sp); } } ThreadedGroup = TG_save; return ap; } /* Find the next thread (first if art > lastart). If articles are selected, ** only choose from threads with selected articles. */ void next_subject() { register SUBJECT *sp; register ARTICLE *ap; sp = ((ap = artp) ? ap->subj->next : first_subject); for (; sp; sp = sp->next) { if (sp->flags & SF_VISIT) { if ((ap = subj_art(sp)) != Nullart) { art = article_num(ap); artp = ap; return; } reread = FALSE; } } artp = Nullart; art = lastart+1; forcelast = TRUE; } /* Find previous thread (or last if artp == NULL). If articles are selected, ** only choose from threads with selected articles. */ void prev_subject() { register SUBJECT *sp; register ARTICLE *ap; sp = ((ap = artp) ? ap->subj->prev : last_subject); for (; sp; sp = sp->prev) { if (sp->flags & SF_VISIT) { if ((ap = subj_art(sp)) != Nullart) { art = article_num(ap); artp = ap; return; } reread = FALSE; } } artp = Nullart; art = lastart+1; forcelast = TRUE; } /* Find artp's parent or oldest ancestor. Returns FALSE if no such ** article. Sets art and artp otherwise. */ bool find_parent(keep_going) bool_int keep_going; { register ARTICLE *ap = artp; if (!ap->parent) return FALSE; do { ap = ap->parent; } while (keep_going && ap->parent); if (((artp = ap)->flags & AF_TMPMEM) == AF_TMPMEM) art = 0; else art = article_num(ap); return TRUE; } /* Find artp's first child or youngest decendent. Returns FALSE if no ** such article. Sets art and artp otherwise. */ bool find_leaf(keep_going) bool_int keep_going; { register ARTICLE *ap = artp; if (!ap->child1) return FALSE; do { ap = ap->child1; } while (keep_going && ap->child1); if (((artp = ap)->flags & AF_TMPMEM) == AF_TMPMEM) art = 0; else art = article_num(ap); return TRUE; } static ARTICLE *first_sib(), *last_sib(); /* Find the next "sibling" of artp, including cousins that are the ** same distance down the thread as we are. Returns FALSE if no such ** article. Sets art and artp otherwise. */ bool find_next_sib() { ARTICLE *ta, *tb; int ascent; ascent = 0; ta = artp; for (;;) { while (ta->sibling) { ta = ta->sibling; if (tb = first_sib(ta, ascent)) { if (((artp = tb)->flags & AF_TMPMEM) == AF_TMPMEM) art = 0; else art = article_num(tb); return TRUE; } } if (!(ta = ta->parent)) break; ascent++; } return FALSE; } /* A recursive routine to find the first node at the proper depth. This ** article is at depth 0. */ static ARTICLE * first_sib(ta, depth) ARTICLE *ta; int depth; { ARTICLE *tb; if (!depth) return ta; for (;;) { if (ta->child1 && (tb = first_sib(ta->child1, depth-1))) return tb; if (!ta->sibling) return Nullart; ta = ta->sibling; } } /* Find the previous "sibling" of artp, including cousins that are ** the same distance down the thread as we are. Returns FALSE if no ** such article. Sets art and artp otherwise. */ bool find_prev_sib() { ARTICLE *ta, *tb; int ascent; ascent = 0; ta = artp; for (;;) { tb = ta; if (ta->parent) ta = ta->parent->child1; else ta = ta->subj->thread; if (tb = last_sib(ta, ascent, tb)) { if (((artp = tb)->flags & AF_TMPMEM) == AF_TMPMEM) art = 0; else art = article_num(tb); return TRUE; } if (!(ta = ta->parent)) break; ascent++; } return FALSE; } /* A recursive routine to find the last node at the proper depth. This ** article is at depth 0. */ static ARTICLE * last_sib(ta, depth, limit) ARTICLE *ta; int depth; ARTICLE *limit; { ARTICLE *tb, *tc; if (ta == limit) return Nullart; if (ta->sibling) { tc = ta->sibling; if (tc != limit && (tb = last_sib(tc,depth,limit))) return tb; } if (!depth) return ta; if (ta->child1) return last_sib(ta->child1, depth-1, limit); return Nullart; } /* Get each subject's article count; count total articles and selected ** articles (use sel_rereading to determine whether to count read or ** unread articles); deselect any subjects we find that are empty if ** CS_UNSELECT or CS_UNSEL_STORE is specified. If mode is CS_RESELECT ** is specified, the selections from the last CS_UNSEL_STORE are ** reselected. */ void count_subjects(mode) int mode; { register int count, sel_count; register ARTICLE *ap; register SUBJECT *sp; int desired_flags = (sel_rereading? AF_READ : 0); time_t subjdate; article_count = selected_count = selected_subj_cnt = 0; if (last_cached >= lastart) firstart = lastart+1; for (sp = first_subject; sp; sp = sp->next) sp->flags &= ~SF_VISIT; for (sp = first_subject; sp; sp = sp->next) { subjdate = 0; count = sel_count = 0; for (ap = sp->articles; ap; ap = ap->subj_next) { if ((ap->flags & (AF_MISSING|AF_READ)) == desired_flags) { count++; if (ap->flags & sel_mask) sel_count++; if (!subjdate) subjdate = ap->date; if (article_num(ap) < firstart) firstart = article_num(ap); } } if (mode == CS_UNSEL_STORE) { if (sp->flags & SF_SEL) sp->flags |= SF_OLDSEL; else sp->flags &= ~SF_OLDSEL; } else if (mode == CS_RESELECT) { if (sp->flags & SF_OLDSEL) sp->flags |= SF_SEL; else sp->flags &= ~SF_SEL; } sp->misc = count; if (subjdate) sp->date = subjdate; article_count += count; if (sel_count) { sp->flags = (sp->flags & ~(SF_SEL|SF_DEL)) | sel_mask; selected_count += sel_count; selected_subj_cnt++; } else if (mode >= CS_UNSELECT) sp->flags &= ~sel_mask; else if (sp->flags & sel_mask) { sp->flags &= ~SF_DEL; selected_subj_cnt++; } if (count && (!selected_only || (sp->flags & sel_mask))) { sp->flags |= SF_VISIT; if (sel_mode == SM_THREAD) { if ((ap = sp->thread) != NULL) ap->subj->flags |= SF_VISIT; } } } if (mode && !article_count && !selected_only) { for (sp = first_subject; sp; sp = sp->next) sp->flags |= SF_VISIT; } } int subjorder_subject(spp1, spp2) register SUBJECT **spp1; register SUBJECT **spp2; { return strCASEcmp((*spp1)->str+4, (*spp2)->str+4) * sel_direction; } int subjorder_date(spp1, spp2) register SUBJECT **spp1; register SUBJECT **spp2; { return (int)((*spp1)->date - (*spp2)->date) * sel_direction; } int subjorder_count(spp1, spp2) register SUBJECT **spp1; register SUBJECT **spp2; { int eq; if ((eq = (int)((*spp1)->misc - (*spp2)->misc)) != 0) return eq * sel_direction; return (int)((*spp1)->date - (*spp2)->date) * sel_direction; } int threadorder_subject(spp1, spp2) SUBJECT **spp1; SUBJECT **spp2; { register ARTICLE *t1 = (*spp1)->thread; register ARTICLE *t2 = (*spp2)->thread; if (t1 != t2 && t1 && t2) return strCASEcmp(t1->subj->str+4, t2->subj->str+4) * sel_direction; return (int)((*spp1)->date - (*spp2)->date) * sel_direction; } int threadorder_date(spp1, spp2) SUBJECT **spp1; SUBJECT **spp2; { register ARTICLE *t1 = (*spp1)->thread; register ARTICLE *t2 = (*spp2)->thread; if (t1 != t2 && t1 && t2) { register SUBJECT *sp1, *sp2; int eq; if (!(sp1 = t1->subj)->misc) for (sp1=sp1->thread_link; sp1 != t1->subj; sp1=sp1->thread_link) if (sp1->misc) break; if (!(sp2 = t2->subj)->misc) for (sp2=sp2->thread_link; sp2 != t2->subj; sp2=sp2->thread_link) if (sp2->misc) break; if ((eq = (int)(sp1->date - sp2->date) * sel_direction) != 0) return eq; return strCASEcmp(sp1->str+4, sp2->str+4) * sel_direction; } return (int)((*spp1)->date - (*spp2)->date) * sel_direction; } int threadorder_count(spp1, spp2) SUBJECT **spp1; SUBJECT **spp2; { register int size1 = (*spp1)->misc; register int size2 = (*spp2)->misc; if ((*spp1)->thread != (*spp2)->thread) { register SUBJECT *sp; for (sp = (*spp1)->thread_link; sp != *spp1; sp = sp->thread_link) size1 += sp->misc; for (sp = (*spp2)->thread_link; sp != *spp2; sp = sp->thread_link) size2 += sp->misc; } if (size1 != size2) return (size1 - size2) * sel_direction; return threadorder_date(spp1, spp2); } /* Sort the subjects according to the chosen order. */ void sort_subjects() { register SUBJECT *sp; register int i; SUBJECT **lp, **subj_list; int (*sort_procedure)(); /* If we don't have at least two subjects, we're done! */ if (!first_subject || !first_subject->next) return; switch (sel_sort) { case SS_DATE: case SS_AUTHOR: case SS_GROUPS: sort_procedure = (sel_mode == SM_THREAD? threadorder_date : subjorder_date); break; case SS_SUBJECT: sort_procedure = (sel_mode == SM_THREAD? threadorder_subject : subjorder_subject); break; case SS_COUNT: sort_procedure = (sel_mode == SM_THREAD? threadorder_count : subjorder_count); break; } subj_list = (SUBJECT**)safemalloc(subject_count * sizeof (SUBJECT*)); for (lp = subj_list, sp = first_subject; sp; sp = sp->next) *lp++ = sp; assert(lp - subj_list == subject_count); qsort(subj_list, subject_count, sizeof (SUBJECT*), sort_procedure); first_subject = sp = subj_list[0]; sp->prev = Nullsubj; for (i = subject_count, lp = subj_list; --i; lp++) { lp[0]->next = lp[1]; lp[1]->prev = lp[0]; if (sel_mode == SM_THREAD) { if (lp[0]->thread == lp[1]->thread) lp[0]->thread_link = lp[1]; else { lp[0]->thread_link = sp; sp = lp[1]; } } } last_subject = lp[0]; last_subject->next = Nullsubj; if (sel_mode == SM_THREAD) last_subject->thread_link = sp; free((char*)subj_list); } int artorder_date(art1, art2) register ARTICLE **art1; register ARTICLE **art2; { return (int)((*art1)->date - (*art2)->date) * sel_direction; } int artorder_subject(art1, art2) register ARTICLE **art1; register ARTICLE **art2; { if ((*art1)->subj == (*art2)->subj) return (int)((*art1)->date - (*art2)->date); return strCASEcmp((*art1)->subj->str + 4, (*art2)->subj->str + 4) * sel_direction; } int artorder_author(art1, art2) register ARTICLE **art1; register ARTICLE **art2; { int eq; if ((eq = strCASEcmp((*art1)->from, (*art2)->from)) != 0) return eq * sel_direction; return (int)((*art1)->date - (*art2)->date); } int artorder_groups(art1, art2) register ARTICLE **art1; register ARTICLE **art2; { if ((*art1)->subj == (*art2)->subj) return (int)((*art1)->date - (*art2)->date); return (int)((*art1)->subj->date - (*art2)->subj->date) * sel_direction; } /* Sort the articles according to the chosen order. */ void sort_articles() { int (*sort_procedure)(); build_artptrs(); /* If we don't have at least two articles, we're done! */ if (article_count <2) return; switch (sel_sort) { case SS_DATE: case SS_COUNT: sort_procedure = artorder_date; break; case SS_SUBJECT: sort_procedure = artorder_subject; break; case SS_AUTHOR: sort_procedure = artorder_author; break; case SS_GROUPS: sort_procedure = artorder_groups; break; } if (sel_page_app) sel_last_ap = *sel_page_app; sel_page_app = 0; qsort(artptr_list, article_count, sizeof (ARTICLE*), sort_procedure); } static long artptr_list_size = 0; static void build_artptrs() { ARTICLE **app, *ap; long count = article_count; int desired_flags = (sel_rereading? AF_READ : 0); if (!artptr_list || artptr_list_size != count) { artptr_list = (ARTICLE**)saferealloc((char*)artptr_list, (MEM_SIZE)count * sizeof (ARTICLE*)); artptr_list_size = count; } for (app = artptr_list, ap = article_list; count; ap++) { if ((ap->flags & (AF_MISSING|AF_READ)) == desired_flags) { *app++ = ap; count--; } } }