4.4BSD/usr/src/contrib/news/trn3/rthread.c
/* $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--;
}
}
}