/* rthread.c
*/
/* This software is copyrighted as detailed in the LICENSE file. */
#include "EXTERN.h"
#include "common.h"
#include "list.h"
#include "intrp.h"
#include "trn.h"
#include "hash.h"
#include "cache.h"
#include "bits.h"
#include "ngdata.h"
#include "nntpclient.h"
#include "datasrc.h"
#include "nntp.h"
#include "ng.h"
#include "rcln.h"
#include "search.h"
#include "artstate.h"
#include "rcstuff.h"
#include "final.h"
#include "kfile.h"
#include "head.h"
#include "util.h"
#include "util2.h"
#include "rt-mt.h"
#include "rt-ov.h"
#ifdef SCORE
#include "score.h"
#endif
#include "rt-page.h"
#include "rt-process.h"
#include "rt-select.h"
#include "rt-util.h"
#include "rt-wumpus.h"
#include "INTERN.h"
#include "rthread.h"
#include "rthread.ih"
void
thread_init()
{
}
/* Generate the thread data we need for this group. We must call
** thread_close() before calling this again.
*/
void
thread_open()
{
bool find_existing = !first_subject;
time_t save_ov_opened;
ARTICLE* ap;
if (!msgid_hash)
msgid_hash = hashcreate(1999, 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);
if ((datasrc->flags & DF_TRY_THREAD) && !first_subject) {
if (mt_data() < 0)
return;
}
if ((datasrc->flags & DF_TRY_OVERVIEW) && !cached_all_in_range) {
if (thread_always) {
spin_todo = spin_estimate = lastart - absfirst + 1;
(void) ov_data(absfirst, lastart, FALSE);
if (datasrc->ov_opened && find_existing && datasrc->over_dir == NULL) {
mark_missing_articles();
rc_to_bits();
find_existing = FALSE;
}
}
else {
spin_todo = lastart - firstart + 1;
spin_estimate = ngptr->toread;
if (firstart > lastart) {
/* If no unread articles, see if ov. exists as fast as possible */
(void) ov_data(absfirst, absfirst, FALSE);
cached_all_in_range = FALSE;
} else
(void) ov_data(firstart, lastart, FALSE);
}
}
if (find_existing) {
find_existing_articles();
mark_missing_articles();
rc_to_bits();
}
for (ap = article_ptr(article_first(firstart));
ap && article_num(ap) <= last_cached;
ap = article_nextp(ap))
{
if ((ap->flags & (AF_EXISTS|AF_CACHED)) == AF_EXISTS)
(void) parseheader(article_num(ap));
}
if (last_cached > lastart) {
ngptr->toread += (ART_UNREAD)(last_cached-lastart);
/* ensure getngsize() knows the new maximum */
ngptr->ngmax = lastart = last_cached;
}
article_list->high = lastart;
for (ap = article_ptr(article_first(absfirst));
ap && article_num(ap) <= lastart;
ap = article_nextp(ap))
{
if (ap->flags & AF_CACHED)
check_poster(ap);
}
save_ov_opened = datasrc->ov_opened;
datasrc->ov_opened = 0; /* avoid trying to call ov_data twice for high arts */
thread_grow(); /* thread any new articles not yet in the database */
datasrc->ov_opened = save_ov_opened;
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 && thread_always)
cache_range(last_cached + 1, lastart);
count_subjects(CS_NORM);
if (artptr_list)
sort_articles();
else
sort_subjects();
}
void
thread_close()
{
curr_artp = artp = NULL;
init_tree(); /* free any tree lines */
update_thread_kfile();
if (msgid_hash)
hashwalk(msgid_hash, cleanup_msgid_hash, 0);
sel_page_sp = 0;
sel_page_app = 0;
sel_last_ap = 0;
sel_last_sp = 0;
selected_only = FALSE;
sel_exclusive = 0;
ov_close();
}
static int
cleanup_msgid_hash(keylen, data, extra)
int keylen;
HASHDATUM* data;
int extra;
{
register ARTICLE* ap = (ARTICLE*)data->dat_ptr;
int ret = -1;
if (ap) {
if (data->dat_len)
return 0;
if ((kf_state & KFS_GLOBAL_THREADFILE) && ap->autofl) {
data->dat_ptr = ap->msgid;
data->dat_len = ap->autofl;
ret = 0;
ap->msgid = NULL;
}
if (ap->flags & AF_TMPMEM) {
clear_article(ap);
free((char*)ap);
}
}
return ret;
}
void
top_article()
{
art = lastart+1;
artp = NULL;
inc_art(selected_only, FALSE);
if (art > lastart && last_cached < lastart)
art = firstart;
}
ARTICLE*
first_art(sp)
register SUBJECT* sp;
{
register ARTICLE* ap = (ThreadedGroup? sp->thread : sp->articles);
if (ap && !(ap->flags & AF_EXISTS)) {
oneless(ap);
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_EXISTS)) {
oneless(ap);
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 = (rereading? 0 : SF_VISIT);
/* Use the explicit article-order if it exists */
if (artptr_list) {
ARTICLE** limit = artptr_list + artptr_list_size;
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_UNREAD))
|| (sel_flag && !(ap->flags & AF_SEL)));
if (artptr < limit) {
artp = *artptr;
art = article_num(artp);
} else {
artp = NULL;
art = lastart+1;
artptr = artptr_list;
}
return;
}
/* Use subject- or thread-order when possible */
if (ThreadedGroup || srchahead) {
register SUBJECT* sp;
if (ap)
sp = ap->subj;
else
sp = next_subj((SUBJECT*)NULL, subj_mask);
if (!sp)
goto num_inc;
do {
if (ap)
ap = next_art(ap);
else
ap = first_art(sp);
while (!ap) {
sp = next_subj(sp, subj_mask);
if (!sp)
break;
ap = first_art(sp);
}
} while (ap && ((!rereading && !(ap->flags & AF_UNREAD))
|| (sel_flag && !(ap->flags & AF_SEL))));
if ((artp = ap) != NULL)
art = article_num(ap);
else {
if (art <= last_cached)
art = last_cached+1;
else
art++;
if (art <= lastart)
artp = article_ptr(art);
else
art = lastart+1;
}
return;
}
/* Otherwise, just increment through the art numbers */
num_inc:
if (!ap)
art = firstart-1;
for (;;) {
art = article_next(art);
if (art > lastart) {
art = lastart+1;
ap = NULL;
break;
}
ap = article_ptr(art);
if (!(ap->flags & AF_EXISTS))
oneless(ap);
else if ((rereading || (ap->flags & AF_UNREAD))
&& (!sel_flag || (ap->flags & AF_SEL)))
break;
}
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 = (rereading? 0 : SF_VISIT);
/* Use the explicit article-order if it exists */
if (artptr_list) {
ARTICLE** limit = artptr_list + artptr_list_size;
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_UNREAD))
|| (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
sp = prev_subj((SUBJECT*)NULL, subj_mask);
if (!sp)
goto num_dec;
do {
if (ap)
ap = prev_art(ap);
else
ap = last_art(sp);
while (!ap) {
sp = prev_subj(sp, subj_mask);
if (!sp)
break;
ap = last_art(sp);
}
} while (ap && ((!rereading && !(ap->flags & AF_UNREAD))
|| (sel_flag && !(ap->flags & AF_SEL))));
if ((artp = ap) != NULL)
art = article_num(ap);
else
art = absfirst-1;
return;
}
/* Otherwise, just decrement through the art numbers */
num_dec:
for (;;) {
art = article_prev(art);
if (art < absfirst) {
art = absfirst-1;
ap = NULL;
break;
}
ap = article_ptr(art);
if (!(ap->flags & AF_EXISTS))
oneless(ap);
else if ((rereading || (ap->flags & AF_UNREAD))
&& (!sel_flag || (ap->flags & AF_SEL)))
break;
}
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 NULL;
}
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 NULL;
}
ap = ap->sibling;
} while (breadth_first);
done:
if (ap && !(ap->flags & AF_EXISTS)) {
oneless(ap);
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 = NULL;
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_EXISTS)) {
oneless(ap);
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;
if (!ap)
return FALSE;
do {
ap = ap->subj_next;
if (!ap) {
if (!art)
art = firstart;
return FALSE;
}
} while (!ALLBITS(ap->flags, AF_EXISTS | AF_UNREAD)
|| (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;
register ARTICLE* ap2;
if (!ap)
return FALSE;
do {
ap2 = ap->subj->articles;
if (ap2 == ap)
ap = NULL;
else {
while (ap2 && ap2->subj_next != ap)
ap2 = ap2->subj_next;
ap = ap2;
}
if (!ap) {
if (!art)
art = lastart;
return FALSE;
}
} while (!(ap->flags & AF_EXISTS)
|| (selected_only && !(ap->flags & AF_SEL)));
artp = ap;
art = article_num(ap);
return TRUE;
}
SUBJECT*
next_subj(sp, subj_mask)
register SUBJECT* sp;
int subj_mask;
{
if (!sp)
sp = first_subject;
else if (sel_mode == SM_THREAD) {
ARTICLE* ap = sp->thread;
do {
sp = sp->next;
} while (sp && sp->thread == ap);
}
else
sp = sp->next;
while (sp && (sp->flags & subj_mask) != subj_mask) {
sp = sp->next;
}
return sp;
}
SUBJECT*
prev_subj(sp, subj_mask)
register SUBJECT* sp;
int subj_mask;
{
if (!sp)
sp = last_subject;
else if (sel_mode == SM_THREAD) {
ARTICLE* ap = sp->thread;
do {
sp = sp->prev;
} while (sp && sp->thread == ap);
}
else
sp = sp->prev;
while (sp && (sp->flags & subj_mask) != subj_mask) {
sp = sp->prev;
}
return sp;
}
/* Select a single article.
*/
void
select_article(ap, auto_flags)
register ARTICLE* ap;
int auto_flags;
{
int desired_flags = (sel_rereading? AF_EXISTS : (AF_EXISTS|AF_UNREAD));
#ifdef VERBOSE
bool echo = (auto_flags & ALSO_ECHO) != 0;
#endif
auto_flags &= AUTO_SELS;
if ((ap->flags & (AF_EXISTS|AF_UNREAD)) == desired_flags) {
if (!(ap->flags & sel_mask)) {
selected_count++;
#ifdef VERBOSE
IF(verbose && echo && gmode != 's')
fputs("\tSelected",stdout);
#endif
}
ap->flags = (ap->flags & ~AF_DEL) | sel_mask;
}
if (auto_flags)
change_auto_flags(ap, auto_flags);
if (ap->subj) {
if (!(ap->subj->flags & sel_mask))
selected_subj_cnt++;
ap->subj->flags = (ap->subj->flags&~SF_DEL) | sel_mask | SF_VISIT;
}
selected_only = (selected_only || selected_count != 0);
}
/* Select this article's subject.
*/
void
select_arts_subject(ap, auto_flags)
register ARTICLE* ap;
int auto_flags;
{
if (ap->subj && ap->subj->articles)
select_subject(ap->subj, auto_flags);
else
select_article(ap, auto_flags);
}
/* Select all the articles in a subject.
*/
void
select_subject(subj, auto_flags)
SUBJECT* subj;
int auto_flags;
{
register ARTICLE* ap;
int desired_flags = (sel_rereading? AF_EXISTS : (AF_EXISTS|AF_UNREAD));
int old_count = selected_count;
auto_flags &= AUTO_SELS;
for (ap = subj->articles; ap; ap = ap->subj_next) {
if ((ap->flags & (AF_EXISTS|AF_UNREAD|sel_mask)) == desired_flags) {
ap->flags |= sel_mask;
selected_count++;
}
if (auto_flags)
change_auto_flags(ap, auto_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;
selected_only = TRUE;
} else
subj->flags |= SF_WASSELECTED;
}
/* Select this article's thread.
*/
void
select_arts_thread(ap, auto_flags)
register ARTICLE* ap;
int auto_flags;
{
if (ap->subj && ap->subj->thread)
select_thread(ap->subj->thread, auto_flags);
else
select_arts_subject(ap, auto_flags);
}
/* Select all the articles in a thread.
*/
void
select_thread(thread, auto_flags)
register ARTICLE* thread;
int auto_flags;
{
register SUBJECT* sp;
sp = thread->subj;
do {
select_subject(sp, auto_flags);
sp = sp->thread_link;
} while (sp != thread->subj);
}
/* Select the subthread attached to this article.
*/
void
select_subthread(ap, auto_flags)
register ARTICLE* ap;
int auto_flags;
{
register ARTICLE* limit;
SUBJECT* subj;
int desired_flags = (sel_rereading? AF_EXISTS : (AF_EXISTS|AF_UNREAD));
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;
}
}
auto_flags &= AUTO_SELS;
for (; ap != limit; ap = bump_art(ap)) {
if ((ap->flags & (AF_EXISTS|AF_UNREAD|sel_mask)) == desired_flags) {
ap->flags |= sel_mask;
selected_count++;
}
if (auto_flags)
change_auto_flags(ap, auto_flags);
}
if (subj && selected_count > old_count) {
if (!(subj->flags & sel_mask))
selected_subj_cnt++;
subj->flags = (subj->flags & ~SF_DEL) | sel_mask | SF_VISIT;
selected_only = TRUE;
}
}
/* Deselect a single article.
*/
void
deselect_article(ap, auto_flags)
register ARTICLE* ap;
int auto_flags;
{
#ifdef VERBOSE
bool echo = (auto_flags & ALSO_ECHO) != 0;
#endif
auto_flags &= AUTO_SELS;
if (ap->flags & sel_mask) {
ap->flags &= ~sel_mask;
if (!selected_count--)
selected_count = 0;
#ifdef VERBOSE
IF(verbose && echo && gmode != 's')
fputs("\tDeselected",stdout);
#endif
}
if (sel_rereading && sel_mode == SM_ARTICLE)
ap->flags |= AF_DEL;
}
/* Deselect this article's subject.
*/
void
deselect_arts_subject(ap)
register ARTICLE* ap;
{
if (ap->subj && ap->subj->articles)
deselect_subject(ap->subj);
else
deselect_article(ap, 0);
}
/* 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 this article's thread.
*/
void
deselect_arts_thread(ap)
register ARTICLE* ap;
{
if (ap->subj && ap->subj->thread)
deselect_thread(ap->subj->thread);
else
deselect_arts_subject(ap);
}
/* 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 this article's subject.
*/
void
kill_arts_subject(ap, auto_flags)
register ARTICLE* ap;
int auto_flags;
{
if (ap->subj && ap->subj->articles)
kill_subject(ap->subj, auto_flags);
else {
if (auto_flags & SET_TORETURN)
delay_unmark(ap);
set_read(ap);
auto_flags &= AUTO_KILLS;
if (auto_flags)
change_auto_flags(ap, auto_flags);
}
}
/* Kill all unread articles attached to the given subject.
*/
void
kill_subject(subj, auto_flags)
SUBJECT* subj;
int auto_flags;
{
register ARTICLE* ap;
register int killmask = (auto_flags & AFFECT_ALL)? 0 : sel_mask;
char toreturn = (auto_flags & SET_TORETURN) != 0;
auto_flags &= AUTO_KILLS;
for (ap = subj->articles; ap; ap = ap->subj_next) {
if (toreturn)
delay_unmark(ap);
if ((ap->flags & (AF_UNREAD|killmask)) == AF_UNREAD)
set_read(ap);
if (auto_flags)
change_auto_flags(ap, auto_flags);
}
subj->flags &= ~(SF_VISIT | SF_WASSELECTED);
}
/* Kill all unread articles attached to this article's thread.
*/
void
kill_arts_thread(ap, auto_flags)
register ARTICLE* ap;
int auto_flags;
{
if (ap->subj && ap->subj->thread)
kill_thread(ap->subj->thread, auto_flags);
else
kill_arts_subject(ap, auto_flags);
}
/* Kill all unread articles attached to the given thread.
*/
void
kill_thread(thread, auto_flags)
register ARTICLE* thread;
int auto_flags;
{
register SUBJECT* sp;
sp = thread->subj;
do {
kill_subject(sp, auto_flags);
sp = sp->thread_link;
} while (sp != thread->subj);
}
/* Kill the subthread attached to this article.
*/
void
kill_subthread(ap, auto_flags)
register ARTICLE* ap;
int auto_flags;
{
register ARTICLE* limit;
char toreturn = (auto_flags & SET_TORETURN) != 0;
if (!ap)
return;
for (limit = ap; limit; limit = limit->parent) {
if (limit->sibling) {
limit = limit->sibling;
break;
}
}
auto_flags &= AUTO_KILLS;
for (; ap != limit; ap = bump_art(ap)) {
if (toreturn)
delay_unmark(ap);
if (ALLBITS(ap->flags, AF_EXISTS | AF_UNREAD))
set_read(ap);
if (auto_flags)
change_auto_flags(ap, auto_flags);
}
}
/* Unkill all the articles attached to the given subject.
*/
void
unkill_subject(subj)
SUBJECT* subj;
{
register ARTICLE* ap;
int save_sel_count = selected_count;
for (ap = subj->articles; ap; ap = ap->subj_next) {
if (sel_rereading) {
if (ALLBITS(ap->flags, AF_DELSEL | AF_EXISTS)) {
if (!(ap->flags & AF_UNREAD))
ngptr->toread++;
if (selected_only && !(ap->flags & AF_SEL))
selected_count++;
ap->flags = (ap->flags & ~AF_DELSEL) | AF_SEL|AF_UNREAD;
} else
ap->flags &= ~(AF_DEL|AF_DELSEL);
} else {
if ((ap->flags & (AF_EXISTS|AF_UNREAD)) == AF_EXISTS)
onemore(ap);
if (selected_only && (ap->flags & (AF_SEL|AF_UNREAD)) == AF_UNREAD) {
ap->flags = (ap->flags & ~AF_DEL) | AF_SEL;
selected_count++;
}
}
}
if (selected_count != save_sel_count
&& selected_only && !(subj->flags & SF_SEL)) {
subj->flags |= SF_SEL | SF_VISIT | SF_WASSELECTED;
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_EXISTS|AF_UNREAD)) == AF_EXISTS)
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;
selected_only = (selected_only || selected_count != 0);
}
/* Clear the auto flags in 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)
clear_auto_flags(ap);
}
/* Clear the auto flags in 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);
}
/* Clear the auto flags in 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))
clear_auto_flags(ap);
}
ARTICLE*
subj_art(sp)
SUBJECT* sp;
{
register ARTICLE* ap = NULL;
int art_mask = (selected_only? (AF_SEL|AF_UNREAD) : AF_UNREAD);
bool TG_save = ThreadedGroup;
ThreadedGroup = (sel_mode == SM_THREAD);
ap = first_art(sp);
while (ap && (ap->flags & art_mask) != art_mask)
ap = next_art(ap);
if (!ap) {
reread = TRUE;
ap = first_art(sp);
if (selected_only) {
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
visit_next_thread()
{
register SUBJECT* sp;
register ARTICLE* ap = artp;
sp = (ap? ap->subj : NULL);
while ((sp = next_subj(sp, SF_VISIT)) != NULL) {
if ((ap = subj_art(sp)) != NULL) {
art = article_num(ap);
artp = ap;
return;
}
reread = FALSE;
}
artp = NULL;
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
visit_prev_thread()
{
register SUBJECT* sp;
register ARTICLE* ap = artp;
sp = (ap? ap->subj : NULL);
while ((sp = prev_subj(sp, SF_VISIT)) != NULL) {
if ((ap = subj_art(sp)) != NULL) {
art = article_num(ap);
artp = ap;
return;
}
reread = FALSE;
}
artp = NULL;
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);
artp = ap;
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);
artp = ap;
art = article_num(ap);
return TRUE;
}
/* 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;
ARTICLE* tb;
int ascent;
ascent = 0;
ta = artp;
for (;;) {
while (ta->sibling) {
ta = ta->sibling;
if ((tb = first_sib(ta, ascent)) != NULL) {
artp = tb;
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 NULL;
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;
ARTICLE* 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)) != NULL) {
artp = tb;
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;
ARTICLE* tc;
if (ta == limit)
return NULL;
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 NULL;
}
/* 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(cmode)
int cmode;
{
register int count, sel_count;
register ARTICLE* ap;
register SUBJECT* sp;
int desired_flags = (sel_rereading? AF_EXISTS : (AF_EXISTS|AF_UNREAD));
time_t subjdate;
obj_count = selected_count = selected_subj_cnt = 0;
if (last_cached >= lastart)
firstart = lastart+1;
switch (cmode) {
case CS_RETAIN:
break;
case CS_UNSEL_STORE:
for (sp = first_subject; sp; sp = sp->next) {
if (sp->flags & SF_VISIT)
sp->flags = (sp->flags & ~SF_VISIT) | SF_OLDVISIT;
else
sp->flags &= ~SF_OLDVISIT;
}
break;
case CS_RESELECT:
for (sp = first_subject; sp; sp = sp->next) {
if (sp->flags & SF_OLDVISIT)
sp->flags |= SF_VISIT;
else
sp->flags &= ~SF_VISIT;
}
break;
default:
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_EXISTS|AF_UNREAD)) == desired_flags) {
count++;
if (ap->flags & sel_mask)
sel_count++;
if (!subjdate)
subjdate = ap->date;
if (article_num(ap) < firstart)
firstart = article_num(ap);
}
}
sp->misc = count;
if (subjdate)
sp->date = subjdate;
else if (!sp->date && sp->articles)
sp->date = sp->articles->date;
obj_count += count;
if (cmode == CS_RESELECT) {
if (sp->flags & SF_VISIT) {
sp->flags = (sp->flags & ~(SF_SEL|SF_DEL)) | sel_mask;
selected_count += sel_count;
selected_subj_cnt++;
} else
sp->flags &= ~sel_mask;
} else {
if (sel_count
&& (cmode >= CS_UNSEL_STORE || (sp->flags & sel_mask))) {
sp->flags = (sp->flags & ~(SF_SEL|SF_DEL)) | sel_mask;
selected_count += sel_count;
selected_subj_cnt++;
} else if (cmode >= 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 (cmode != CS_RETAIN && cmode != CS_RESELECT
&& !obj_count && !selected_only) {
for (sp = first_subject; sp; sp = sp->next)
sp->flags |= SF_VISIT;
}
}
static int
subjorder_subject(spp1, spp2)
register SUBJECT** spp1;
register SUBJECT** spp2;
{
return strcaseCMP((*spp1)->str+4, (*spp2)->str+4) * sel_direction;
}
static int
subjorder_date(spp1, spp2)
register SUBJECT** spp1;
register SUBJECT** spp2;
{
time_t eq = (*spp1)->date - (*spp2)->date;
return eq? eq > 0? sel_direction : -sel_direction : 0;
}
static int
subjorder_count(spp1, spp2)
register SUBJECT** spp1;
register SUBJECT** spp2;
{
short eq = (*spp1)->misc - (*spp2)->misc;
return eq? eq > 0? sel_direction : -sel_direction : subjorder_date(spp1,spp2);
}
static int
subjorder_lines(spp1, spp2)
register SUBJECT** spp1;
register SUBJECT** spp2;
{
long eq, l1, l2;
l1 = (*spp1)->articles? (*spp1)->articles->lines : 0;
l2 = (*spp2)->articles? (*spp2)->articles->lines : 0;
eq = l1 - l2;
return eq? eq > 0? sel_direction : -sel_direction : subjorder_date(spp1,spp2);
}
#ifdef SCORE
/* for now, highest eligible article score */
static int
subject_score_high(sp)
SUBJECT* sp;
{
ARTICLE* ap;
int desired_flags = (sel_rereading? AF_EXISTS : (AF_EXISTS|AF_UNREAD));
int hiscore = 0;
int hiscore_found = 0;
int sc;
/* find highest score of desired articles */
for (ap = sp->articles; ap; ap = ap->subj_next) {
if ((ap->flags & (AF_EXISTS|AF_UNREAD)) == desired_flags) {
sc = sc_score_art(article_num(ap),FALSE);
if ((!hiscore_found) || (sc>hiscore)) {
hiscore_found = 1;
hiscore = sc;
}
}
}
return hiscore;
}
/* later make a subject-thread score routine */
static int
subjorder_score(spp1, spp2)
register SUBJECT** spp1;
register SUBJECT** spp2;
{
int sc1, sc2;
sc1 = subject_score_high(*spp1);
sc2 = subject_score_high(*spp2);
if (sc1 != sc2)
return (sc2 - sc1) * sel_direction;
return subjorder_date(spp1, spp2);
}
#endif
static 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 subjorder_date(spp1, spp2);
}
static 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;
register SUBJECT* sp2;
long 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 = sp1->date - sp2->date))
return strcaseCMP(sp1->str+4, sp2->str+4);
return eq > 0? sel_direction : -sel_direction;
}
return subjorder_date(spp1, spp2);
}
static 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);
}
static int
threadorder_lines(spp1, spp2)
SUBJECT** spp1;
SUBJECT** spp2;
{
register ARTICLE* t1 = (*spp1)->thread;
register ARTICLE* t2 = (*spp2)->thread;
if (t1 != t2 && t1 && t2) {
register SUBJECT* sp1;
register SUBJECT* sp2;
long eq, l1, l2;
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;
}
}
l1 = sp1->articles? sp1->articles->lines : 0;
l2 = sp2->articles? sp2->articles->lines : 0;
if ((eq = l1 - l2) != 0)
return eq > 0? sel_direction : -sel_direction;
eq = sp1->date - sp2->date;
return eq? eq > 0? sel_direction : -sel_direction : 0;
}
return subjorder_date(spp1, spp2);
}
#ifdef SCORE
static int
thread_score_high(tp)
SUBJECT* tp;
{
SUBJECT* sp;
int hiscore = 0;
int hiscore_found = 0;
int sc;
for (sp = tp->thread_link; ; sp = sp->thread_link) {
sc = subject_score_high(sp);
if ((!hiscore_found) || (sc>hiscore)) {
hiscore_found = 1;
hiscore = sc;
}
/* break *after* doing the last item */
if (tp == sp)
break;
}
return hiscore;
}
static int
threadorder_score(spp1, spp2)
SUBJECT** spp1;
SUBJECT** spp2;
{
int sc1, sc2;
sc1 = sc2 = 0;
if ((*spp1)->thread != (*spp2)->thread) {
sc1 = thread_score_high(*spp1);
sc2 = thread_score_high(*spp2);
}
if (sc1 != sc2)
return (sc2 - sc1) * sel_direction;
return threadorder_date(spp1, spp2);
}
#endif
/* Sort the subjects according to the chosen order.
*/
void
sort_subjects()
{
register SUBJECT* sp;
register int i;
SUBJECT** lp;
SUBJECT** 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:
default:
sort_procedure = (sel_mode == SM_THREAD?
threadorder_date : subjorder_date);
break;
case SS_STRING:
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;
case SS_LINES:
sort_procedure = (sel_mode == SM_THREAD?
threadorder_lines : subjorder_lines);
break;
#ifdef SCORE
/* if SCORE is undefined, use the default above */
case SS_SCORE:
sort_procedure = (sel_mode == SM_THREAD?
threadorder_score : subjorder_score);
break;
#endif
}
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 = NULL;
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[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 = NULL;
if (sel_mode == SM_THREAD)
last_subject->thread_link = sp;
free((char*)subj_list);
}
static int
artorder_date(art1, art2)
register ARTICLE** art1;
register ARTICLE** art2;
{
long eq = (*art1)->date - (*art2)->date;
return eq? eq > 0? sel_direction : -sel_direction : 0;
}
static int
artorder_subject(art1, art2)
register ARTICLE** art1;
register ARTICLE** art2;
{
if ((*art1)->subj == (*art2)->subj)
return artorder_date(art1, art2);
return strcaseCMP((*art1)->subj->str + 4, (*art2)->subj->str + 4)
* sel_direction;
}
static int
artorder_author(art1, art2)
register ARTICLE** art1;
register ARTICLE** art2;
{
int eq = strcaseCMP((*art1)->from, (*art2)->from);
return eq? eq * sel_direction : artorder_date(art1, art2);
}
static int
artorder_number(art1, art2)
register ARTICLE** art1;
register ARTICLE** art2;
{
ART_NUM eq = article_num(*art1) - article_num(*art2);
return eq > 0? sel_direction : -sel_direction;
}
static int
artorder_groups(art1, art2)
register ARTICLE** art1;
register ARTICLE** art2;
{
long eq;
#ifdef DEBUG
assert((*art1)->subj != NULL);
assert((*art2)->subj != NULL);
#endif
if ((*art1)->subj == (*art2)->subj)
return artorder_date(art1, art2);
eq = (*art1)->subj->date - (*art2)->subj->date;
return eq? eq > 0? sel_direction : -sel_direction : 0;
}
static int
artorder_lines(art1, art2)
register ARTICLE** art1;
register ARTICLE** art2;
{
long eq = (*art1)->lines - (*art2)->lines;
return eq? eq > 0? sel_direction : -sel_direction : artorder_date(art1,art2);
}
#ifdef SCORE
static int
artorder_score(art1, art2)
register ARTICLE** art1;
register ARTICLE** art2;
{
int eq = sc_score_art(article_num(*art2),FALSE)
- sc_score_art(article_num(*art1),FALSE);
return eq? eq > 0? sel_direction : -sel_direction : 0;
}
#endif
/* 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 (artptr_list_size < 2)
return;
switch (sel_sort) {
case SS_DATE:
default:
sort_procedure = artorder_date;
break;
case SS_STRING:
sort_procedure = artorder_subject;
break;
case SS_AUTHOR:
sort_procedure = artorder_author;
break;
case SS_NATURAL:
sort_procedure = artorder_number;
break;
case SS_GROUPS:
sort_procedure = artorder_groups;
break;
case SS_LINES:
sort_procedure = artorder_lines;
break;
#ifdef SCORE
case SS_SCORE:
sort_procedure = artorder_score;
break;
#endif
}
sel_page_app = 0;
qsort(artptr_list, artptr_list_size, sizeof (ARTICLE*), sort_procedure);
}
static void
build_artptrs()
{
ARTICLE** app;
ARTICLE* ap;
ART_NUM an;
ART_NUM count = obj_count;
int desired_flags = (sel_rereading? AF_EXISTS : (AF_EXISTS|AF_UNREAD));
if (!artptr_list || artptr_list_size != count) {
artptr_list = (ARTICLE**)saferealloc((char*)artptr_list,
(MEM_SIZE)count * sizeof (ARTICLE*));
artptr_list_size = count;
}
app = artptr_list;
for (an = article_first(absfirst); count; an = article_next(an)) {
ap = article_ptr(an);
if ((ap->flags & (AF_EXISTS|AF_UNREAD)) == desired_flags) {
*app++ = ap;
count--;
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1