rsalz@bbn.com (Rich Salz) (01/04/91)
Submitted-by: Wayne Davison <0004475895@mcimail.com> Posting-number: Volume 23, Issue 63 Archive-name: trn/part04 #! /bin/sh # This is a shell archive. Remove anything before this line, then feed it # into a shell via "sh file" or similar. To overwrite existing files, # type "sh file -c". # The tool that generated this appeared in the comp.sources.unix newsgroup; # send mail to comp-sources-unix@uunet.uu.net if you want that tool. # Contents: mt-process.c rt-select.c threads.h # Wrapped by rsalz@litchi.bbn.com on Thu Dec 27 11:34:02 1990 PATH=/bin:/usr/bin:/usr/ucb ; export PATH echo If this archive is complete, you will see the following message: echo ' "shar: End of archive 4 (of 14)."' if test -f 'mt-process.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'mt-process.c'\" else echo shar: Extracting \"'mt-process.c'\" \(34673 characters\) sed "s/^X//" >'mt-process.c' <<'END_OF_FILE' X/* $Header: mt-process.c,v 4.3.3.2 90/08/20 16:40:31 davison Trn $ X** X** $Log: mt-process.c,v $ X** Revision 4.3.3.2 90/08/20 16:40:31 davison X** Added check of caught_interrupt flag into main loops. X** X** Revision 4.3.3.1 90/07/28 18:04:45 davison X** Initial Trn Release X** X*/ X X#include "EXTERN.h" X#include "common.h" X#include "mthreads.h" X#ifdef SERVER X#include "server.h" X#endif X X#include <time.h> X#ifndef TZSET X# include <sys/timeb.h> X#endif X Xchar buff[1024]; X Xchar references[1024]; X Xchar subject_str[80]; Xbool found_Re; X Xchar author_str[20]; X Xextern int log_verbosity; X Xextern time_t getdate(); X XDOMAIN *next_domain; X Xvoid insert_article(), expire(), trim_roots(), order_roots(), trim_authors(); Xvoid make_root(), use_root(), merge_roots(), set_root(), unlink_root(); Xvoid link_child(), unlink_child(); Xvoid free_article(), free_domain(), free_subject(), free_root(), free_author(); Xvoid get_subject_str(), get_author_str(); XARTICLE *get_article(); XSUBJECT *new_subject(); XAUTHOR *new_author(); X X#ifdef TZSET Xextern time_t tnow; X#else Xextern struct timeb ftnow; X#endif X X#ifndef SERVER Xstatic FILE *fp_article; X#endif X X/* Given the upper/lower bounds of the articles in the current group, add all X** the ones that we don't know about and remove all the ones that have expired. X** The current directory must be the newgroup's spool directory. X*/ Xvoid Xprocess_articles( first_article, last_article ) XART_NUM first_article, last_article; X{ X register char *cp, *str; X register ARTICLE *article; X register ART_NUM i; X time_t date; X int len; X#ifdef SERVER X bool orig_extra = extra_expire; X#endif X extern int errno; X extern int sys_nerr; X extern char *sys_errlist[]; X X if( first_article > (i = total.last+1) ) { X i = first_article; X } X added_count = last_article - i + 1; X expired_count = 0; X X for( ; i <= last_article; i++ ) { X if( caught_interrupt ) { X return; X } X#ifdef SERVER X sprintf( buff, "HEAD %ld", (long)i ); X put_server( buff ); X if( get_server( buff, sizeof buff ) < 0 || *buff == CHAR_FATAL ) { X last_article = i - 1; X extra_expire = FALSE; X break; X } X if( *buff != CHAR_OK ) { X added_count--; X continue; X } X#else X /* Open article in current directory. */ X sprintf( buff, "%ld", (long)i ); X /* Set errno for purely paranoid reasons */ X errno = 0; X if( (fp_article = fopen( buff, "r" )) == Nullfp ) { X /* Missing files are ok -- they've just been expired or canceled */ X if( errno != 0 && errno != ENOENT ) { X if( errno < 0 || errno > sys_nerr ) { X log_error( "Can't open `%s': Error %d.\n", buff, errno ); X } else { X log_error( "Can't open `%s': %s.\n", buff, X sys_errlist[errno] ); X } X } X added_count--; X continue; X } X#endif X X article = Nullart; X *references = '\0'; X *author_str = '\0'; X *subject_str = '\0'; X found_Re = 0; X date = 0; X X#ifdef SERVER X while( get_server( cp = buff, sizeof buff ) == 0 ) { X process_line: X if( *cp == '.' ) { X break; X } X#else X while( (cp = fgets( buff, sizeof buff, fp_article )) != Nullch ) { X process_line: X if( *cp == '\n' ) { /* check for end of header */ X break; /* break out when found */ X } X#endif X if( (unsigned char)*cp <= ' ' ) { /* skip continuation lines */ X continue; /* (except references -- see below) */ X } X if( (str = index( cp, ':' )) == Nullch ) { X break; /* end of header if no colon found */ X } X if( (len = str - cp) > 10 ) { X continue; /* skip keywords > 10 chars */ X } X#ifndef SERVER X cp[strlen(cp)-1] = '\0'; /* remove newline */ X#endif X while( cp < str ) { /* lower-case the keyword */ X if( (unsigned char)*cp <= ' ' ) { /* stop at any whitespace */ X break; X } X if( isupper(*cp) ) { X *cp = tolower(*cp); X } X cp++; X } X *cp = '\0'; X cp = buff; X if( len == 4 && strEQ( cp, "date" ) ) { X#ifdef TZSET X date = getdate( str + 1, tnow, timezone ); X#else X date = getdate( str + 1, ftnow.time, (long) ftnow.timezone ); X#endif X } else X if( len == 4 && strEQ( cp, "from" ) ) { X get_author_str( str + 1 ); X } else X if( len == 7 && strEQ( cp, "subject" ) ) { X get_subject_str( str + 1 ); X } else X if( len == 10 && strEQ( cp, "message-id" ) ) { X if( !article ) { X article = get_article( str + 1 ); X } else { X if( log_verbosity ) { X log_error( "Found multiple Message-IDs! [%ld].\n", X (long)i ); X } X } X } else X if( len == 10 && strEQ( cp, "references" ) ) { X /* include preceding space in saved reference */ X len = strlen( str + 1 ); X bcopy( str + 1, references, len + 1 ); X str = references + len; X /* check for continuation lines */ X#ifdef SERVER X while( get_server( cp = buff, sizeof buff ) == 0 ) { X#else X while( (cp = fgets( buff, sizeof buff, fp_article )) != Nullch ) { X#endif X if( *cp != ' ' && *cp != '\t' ) { X goto process_line; X } X while( *++cp == ' ' || *cp == '\t' ) { X ; X } X *--cp = ' '; X /* If the references are too long, shift them over to X ** always save the most recent ones. X */ X if( (len += strlen( cp )) > 1023 ) { X strcpy( buff, buff + len - 1023 ); X str -= len - 1023; X len = 1023; X } X strcpy( str, cp ); X }/* while */ X break; X }/* if */ X }/* while */ X if( article ) { X insert_article( article, date, i ); X } else { X if( log_verbosity ) { X log_error( "Message-ID line missing! [%ld].\n", (long)i ); X } X } X#ifndef SERVER X fclose( fp_article ); X#endif X } X X if( extra_expire || first_article > total.first ) { X expire( first_article ); X } X if( caught_interrupt ) { X return; X } X trim_roots(); X order_roots(); X trim_authors(); X X total.first = first_article; X total.last = last_article; X#ifdef SERVER X extra_expire = orig_extra; X#endif X} X X/* Search all articles for numbers less than new_first. Traverse the list X** using the domain links so we don't have to deal with the tree structure. X** If extra_expire is true, stat() all valid articles to make sure they are X** really there and expire them if they're not. X*/ Xvoid Xexpire( new_first ) XART_NUM new_first; X{ X register DOMAIN *domain; X register ARTICLE *article, *next_art, *hold; X X for( domain = &unk_domain; domain; domain = next_domain ) { X next_domain = domain->link; X for( article = domain->ids; article; article = next_art ) { X if( caught_interrupt ) { X return; X } X next_art = article->id_link; X if( !article->subject || (article->flags & NEW_ARTICLE) ) { X continue; X } X if( extra_expire && article->num >= new_first ) { X#ifdef SERVER X sprintf( buff, "STAT %ld", (long)article->num ); X put_server( buff ); X if( get_server( buff, sizeof buff ) == 0 && *buff == CHAR_OK ) { X continue; X } X#else X sprintf( buff, "%ld", (long)article->num ); X if( !stat( buff, &filestat ) || errno != ENOENT ) { X continue; X } X#endif X } X if( extra_expire || article->num < new_first ) { X article->subject->count--; X article->subject = 0; X article->author->count--; X article->author = 0; X /* Free expired article if it has no children. Then check X ** if the parent(s) are also fake and can be freed. We'll X ** free any empty roots later. X */ X while( !article->children ) { X hold = article->parent; X unlink_child( article ); X free_article( article ); X if( hold && !hold->subject ) { X if( (article = hold) == next_art ) { X next_art = next_art->id_link; X } X } else { X break; X } X } X expired_count++; X }/* if */ X }/* for */ X }/* for */ X next_domain = Null(DOMAIN*); X} X X/* Trim the article chains down so that we don't have more than one faked X** article between the root any real ones. X*/ Xvoid Xtrim_roots() X{ X register ROOT *root, *last_root; X register ARTICLE *article, *next; X register SUBJECT *subject, *last_subj; X register int found; X X#ifndef lint X last_root = (ROOT *)&root_root; X#else X last_root = Null(ROOT*); X#endif X for( root = root_root; root; root = last_root->link ) { X for( article = root->articles; article; article = article->siblings ) { X /* If an article has no subject, it is a "fake" reference node. X ** If all of its immediate children are also fakes, delete it X ** and graduate the children to the root. If everyone is fake, X ** the chain dies. X */ X while( !article->subject ) { X found = 0; X for( next = article->children; next; next = next->siblings ) { X if( next->subject ) { X found = 1; X break; X } X } X if( !found ) { X /* Remove this faked article and move all its children X ** up to the root. X */ X next = article->children; X unlink_child( article ); X free_article( article ); X for( article = next; article; article = next ) { X next = article->siblings; X article->parent = Nullart; X link_child( article ); X } X article = root->articles; /* start this root over */ X } else { X break; /* else, on to next article */ X } X } X } X /* Free all unused subject strings. Begin by trying to find a X ** subject for the root's pointer. X */ X for( subject = root->subjects; subject && !subject->count; subject = root->subjects ) { X root->subjects = subject->link; X free_subject( subject ); X root->subject_cnt--; X } X /* Then free up any unsed intermediate subjects. X */ X if( (last_subj = subject) != Null(SUBJECT*) ) { X while( (subject = subject->link) != Null(SUBJECT*) ) { X if( !subject->count ) { X last_subj->link = subject->link; X free_subject( subject ); X root->subject_cnt--; X subject = last_subj; X } else { X last_subj = subject; X } X } X } X /* Now, free all roots without articles. Flag unexpeced errors. X */ X if( !root->articles ) { X if( root->subjects ) { X log_error( "** Empty root still had subjects remaining! **\n" ); X } X last_root->link = root->link; X free_root( root ); X } else { X last_root = root; X } X } X} X X/* Descend the author list, find any author names that aren't used X** anymore and free them. X*/ Xvoid Xtrim_authors() X{ X register AUTHOR *author, *last_author; X X#ifndef lint X last_author = (AUTHOR *)&author_root; X#else X last_author = Null(AUTHOR*); X#endif X for( author = author_root; author; author = last_author->link ) { X if( !author->count ) { X last_author->link = author->link; X free_author( author ); X } else { X last_author = author; X } X } X} X X/* Reorder the roots to place the oldest ones first (age determined by X** date of oldest article). X*/ Xvoid Xorder_roots() X{ X register ROOT *root, *next, *search; X X /* If we don't have at least two roots, we're done! */ X if( !(root = root_root) || !(next = root->link) ) { X return; /* RETURN */ X } X /* Break the old list off after the first root, and then start X ** inserting the roots into the list by date. X */ X root->link = Null(ROOT*); X while( (root = next) != Null(ROOT*) ) { X next = next->link; X if( (search = root_root)->articles->date >= root->articles->date ) { X root->link = root_root; X root_root = root; X } else { X while( search->link X && search->link->articles->date < root->articles->date ) { X search = search->link; X } X root->link = search->link; X search->link = root; X } X } X} X X#define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y)) X X/* Parse the subject into 72 characters or less. Remove any "Re[:^]"s from X** the front (noting that it's there), and any "(was: old)" stuff from X** the end. Then, compact multiple whitespace characters into one space, X** trimming leading/trailing whitespace. If it's still too long, unmercifully X** cut it off. We don't bother with subject continuation lines either. X*/ Xvoid Xget_subject_str( str ) Xregister char *str; X{ X register char *cp; X register int len; X X while( *str && (unsigned char)*str <= ' ' ) { X str++; X } X if( !*str ) { X bcopy( "<None>", subject_str, 7 ); X return; /* RETURN */ X } X cp = str; X while( EQ( cp[0], 'r' ) && EQ( cp[1], 'e' ) ) { /* check for Re: */ X cp += 2; X if( *cp == '^' ) { /* allow Re^2: */ X while( *++cp <= '9' && *cp >= '0' ) { X ; X } X } X if( *cp != ':' ) { X break; X } X while( *++cp == ' ' ) { X ; X } X found_Re = 1; X str = cp; X } X /* Remove "(was Re: oldsubject)", because we already know the old subjects. X ** Also match "(Re: oldsubject)". Allow possible spaces after the ('s. X */ X for( cp = str; (cp = index( cp+1, '(' )) != Nullch; ) { X while( *++cp == ' ' ) { X ; X } X if( EQ( cp[0], 'w' ) && EQ( cp[1], 'a' ) && EQ( cp[2], 's' ) X && (cp[3] == ':' || cp[3] == ' ') ) X { X *--cp = '\0'; X break; X } X if( EQ( cp[0], 'r' ) && EQ( cp[1], 'e' ) X && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':')) ) { X *--cp = '\0'; X break; X } X } X /* Copy subject to a temporary string, compacting multiple spaces/tabs */ X for( len = 0, cp = subject_str; len < 72 && *str; len++ ) { X if( (unsigned char)*str <= ' ' ) { X while( *++str && (unsigned char)*str <= ' ' ) { X ; X } X *cp++ = ' '; X } else { X *cp++ = *str++; X } X } X if( cp[-1] == ' ' ) { X cp--; X } X *cp = '\0'; X} X X/* Try to fit the author name in 16 bytes. Use the comment portion in X** parenthesis if present. Cut off non-commented names at the '@' or '%'. X** Then, put as many characters as we can into the 16 bytes, packing multiple X** whitespace characters into a single space. X** We should really implement a nice name shortening algorithm, or simply X** grab the name packing code from nn. X*/ Xvoid Xget_author_str( str ) Xchar *str; X{ X register char *cp, *cp2; X X if( (cp = index( str, '(' )) != Nullch ) { X str = cp+1; X if( (cp = rindex( str, ')' )) != Nullch ) { X *cp = '\0'; X } X } else { X if( (cp = index( str, '@' )) != Nullch ) { X *cp = '\0'; X } X if( (cp = index( str, '%' )) != Nullch ) { X *cp = '\0'; X } X } X for( cp = str, cp2 = author_str; *cp && cp2-author_str < 16; ) { X /* Pack white space and turn ctrl-chars into spaces. */ X if( *cp <= ' ' ) { X while( *++cp && *cp <= ' ' ) { X ; X } X if( cp2 != author_str ) { X *cp2++ = ' '; X } X } else { X *cp2++ = *cp++; X } X } X *cp2 = '\0'; X} X X/* Take a message-id and see if we already know about it. If so, return it. X** If not, create it. We separate the id into its id@domain parts, and X** link all the unique ids to one copy of the domain portion. This saves X** a bit of space. X*/ XARTICLE * Xget_article( msg_id ) Xchar *msg_id; X{ X register DOMAIN *domain; X register ARTICLE *article; X register char *cp, *after_at; X X /* Take message id, break it up into <id@domain>, and try to match it. X */ X while( *msg_id == ' ' ) { X msg_id++; X } X cp = msg_id + strlen( msg_id ) - 1; X if( msg_id >= cp ) { X if( log_verbosity ) { X log_error( "Message-ID is empty!\n" ); X } X return Nullart; X } X if( *msg_id++ != '<' ) { X if( log_verbosity ) { X log_error( "Message-ID doesn't start with '<'.\n" ); X } X msg_id--; X } X if( *cp != '>' ) { X if( log_verbosity ) { X log_error( "Message-ID doesn't end with '>'.\n" ); X } X cp++; X } X *cp = '\0'; X if( msg_id == cp ) { X if( log_verbosity ) { X log_error( "Message-ID is null!\n" ); X } X return Nullart; X } X X if( (after_at = index( msg_id, '@' )) == Nullch ) { X domain = &unk_domain; X } else { X *after_at++ = '\0'; X for( cp = after_at; *cp; cp++ ) { X if( isupper(*cp) ) { X *cp = tolower(*cp); /* lower-case domain portion */ X } X } X *cp = '\0'; X /* Try to find domain name in database. */ X for( domain = unk_domain.link; domain; domain = domain->link ) { X if( strEQ( domain->name, after_at ) ) { X break; X } X } X if( !domain ) { /* if domain doesn't exist, create it */ X register int len = cp - after_at + 1; X domain = (DOMAIN *)safemalloc( sizeof (DOMAIN) ); X total.domain++; X domain->name = safemalloc( len ); X total.string2 += len; X bcopy( after_at, domain->name, len ); X domain->ids = Nullart; X domain->link = unk_domain.link; X unk_domain.link = domain; X } X } X /* Try to find id in this domain. */ X for( article = domain->ids; article; article = article->id_link ) { X if( strEQ( article->id, msg_id ) ) { X break; X } X } X if( !article ) { /* If it doesn't exist, create an article */ X register int len = strlen( msg_id ) + 1; X article = (ARTICLE *)safemalloc( sizeof (ARTICLE) ); X bzero( article, sizeof (ARTICLE) ); X total.article++; X article->num = 0; X article->id = safemalloc( len ); X total.string2 += len; X bcopy( msg_id, article->id, len ); X article->domain = domain; X article->id_link = domain->ids; X domain->ids = article; X } X return article; X} X X/* Take all the data we've accumulated about the article and shove it into X** the article tree at the best place we can possibly imagine. X*/ Xvoid Xinsert_article( article, date, num ) XARTICLE *article; Xtime_t date; XART_NUM num; X{ X register ARTICLE *node, *last; X register char *cp, *end; X int len; X X if( article->subject ) { X if( log_verbosity ) { X log_error( "We've already seen article #%ld (%s@%s)\n", X (long)num, article->id, article->domain->name ); X } X return; /* RETURN */ X } X article->date = date; X article->num = num; X article->flags = NEW_ARTICLE; X X if( !*references && found_Re ) { X if( log_verbosity > 1 ) { X log_error( "Missing reference line! [%ld]\n", (long)num ); X } X } X /* If the article has a non-zero root, it is already in a thread somewhere. X ** Unlink it to try to put it in the best possible spot. X */ X if( article->root ) { X /* Check for a real or shared-fake parent. Articles that have never X ** existed have a num of 0. Expired articles that remain as references X ** have a valid num. (Valid date too, but no subject.) X */ X for( node = article->parent; X node && !node->num && node->child_cnt == 1; X node = node->parent ) X { X ; X } X unlink_child( article ); X if( node ) { /* do we have decent parents? */ X /* Yes: assume that our references are ok, and just reorder us X ** with our siblings by date. X */ X link_child( article ); X use_root( article, article->root ); X /* Freshen the date in any faked parent articles. */ X for( node = article->parent; X node && !node->num && date < node->date; X node = node->parent ) X { X node->date = date; X unlink_child( node ); X link_child( node ); X } X return; /* RETURN */ X } X /* We'll assume that this article has as good or better references X ** than the child that faked us initially. Free the fake reference- X ** chain and process our references as usual. X */ X for( node = article->parent; node; node = node->parent ) { X unlink_child( node ); X free_article( node ); X } X article->parent = Nullart; /* neaten up */ X article->siblings = Nullart; X } X check_references: X if( !*references ) { /* If no references but "Re:" in subject, */ X if( found_Re ) { /* search for a reference in any cited text */ X#ifndef SERVER X for( len = 4; len && fgets( buff, sizeof buff, fp_article ); len-- ) { X if( (cp = index( buff, '<' )) && (end = index( cp, ' ' )) ) { X if( end[-1] == ',' ) { X end--; X } X *end = '\0'; X if( (end = index( cp, '>' )) == Nullch ) { X end = cp + strlen( cp ) - 1; X } X if( valid_message_id( cp, end ) ) { X strcpy( references+1, cp ); X *references = ' '; X if( log_verbosity > 2 ) { X log_error( "Found cited-text reference: '%s' [%ld]\n", X references+1, (long)num ); X } X break; X } X } X } X#endif X } else { X article->flags |= ROOT_ARTICLE; X } X } X /* If we have references, process them from the right end one at a time X ** until we either run into somebody, or we run out of references. X */ X if( *references ) { X last = article; X node = Nullart; X end = references + strlen( references ) - 1; X while( (cp = rindex( references, ' ' )) != Nullch ) { X *cp++ = '\0'; X while( end >= cp && ((unsigned char)*end <= ' ' || *end == ',') ) { X end--; X } X end[1] = '\0'; X /* Quit parsing references if this one is garbage. */ X if( !valid_message_id( cp, end ) ) { X if( log_verbosity ) { X log_error( "Bad ref '%s' [%ld]\n", cp, (long)num ); X } X break; X } X /* Dump all domains that end in '.', such as "..." & "1@DEL." */ X if( end[-1] == '.' ) { X break; X } X node = get_article( cp ); X /* Check for duplicates on the reference line. Brand-new data has X ** no date. Data we just allocated earlier on this line has a X ** date but no root. Special-case the article itself, since it X ** MIGHT have a root. X */ X if( (node->date && !node->root) || node == article ) { X if( log_verbosity ) { X log_error( "Reference line contains duplicates [%ld]\n", X (long)num ); X } X if( (node = last) == article ) { X node = Nullart; X } X continue; X } X last->parent = node; X link_child( last ); X if( node->root ) { X break; X } X node->date = date; X last = node; X end = cp-2; X } X if( !node ) { X *references = '\0'; X goto check_references; X } X /* Check if we ran into anybody that was already linked. If so, we X ** just use their root. X */ X if( node->root ) { X /* See if this article spans the gap between what we thought X ** were two different roots. X */ X if( article->root && article->root != node->root ) { X merge_roots( node->root, article->root ); X /* Set the roots of any children we brought with us. */ X set_root( article, node->root ); X } X use_root( article, node->root ); X } else { X /* We didn't find anybody we knew, so either create a new root or X ** use the article's root if it was previously faked. X */ X if( !article->root ) { X make_root( node ); X use_root( article, node->root ); X } else { X use_root( article, article->root ); X node->root = article->root; X link_child( node ); X } X } X /* Set the roots of the faked articles we created as references. */ X for( node = article->parent; node && !node->root; node = node->parent ) { X node->root = article->root; X } X /* Make sure we didn't circularly link to a child article(!), by X ** ensuring that we run into the root before we run into ourself. X */ X while( node && node->parent != article ) { X node = node->parent; X } X if( node ) { X /* Ugh. Someone's tweaked reference line with an incorrect X ** article order arrived first, and one of our children is X ** really one of our ancestors. Cut off the bogus child branch X ** right where we are and link it to the root. X */ X if( log_verbosity ) { X log_error("Found ancestral child -- fixing.\n"); X } X unlink_child( node ); X node->parent = Nullart; X link_child( node ); X } X } else { X /* The article has no references. Either turn it into a new root, or X ** re-attach fleshed-out (previously faked) article to its old root. X */ X if( !article->root ) { X make_root( article ); X } else { X use_root( article, article->root ); X link_child( article ); X } X } X} X X/* Check if the string we've found looks like a valid message-id reference. X*/ Xint Xvalid_message_id( start, end ) Xregister char *start, *end; X{ X int lower_case; X char *mid; X X if( *end != '>' ) { X /* Compensate for spacecadets who include the header in their X ** subsitution of all '>'s into another citation character. X */ X if( *end == '<' || *end == '-' || *end == '!' || *end == '%' X || *end == ')' || *end == '|' || *end == ':' || *end == '}' X || *end == '*' || *end == '+' || *end == '#' || *end == ']' X || *end == '@' ) { X if( log_verbosity ) { X log_error( "Reference ended in '%c'.\n", *end ); X } X *end = '>'; X } X } X /* Id must be "<...@...>" */ X if( *start != '<' || *end != '>' || (mid = index( start, '@' )) == Nullch X || mid == start+1 || mid+1 == end ) { X return 0; /* RETURN */ X } X /* Try to weed-out non-ids (user@domain) by looking for lower-case without X ** digits in the unique portion. B news ids are all digits; standard C X ** news are digits with mixed case; and Zeeff message ids are any mixture X ** of digits, certain punctuation characters and upper-case. X */ X lower_case = 0; X do { X if( *start <= '9' && *start >= '0' ) { X return 1; /* RETURN */ X } X lower_case = lower_case || (*start >= 'a' && *start <= 'z'); X } while( ++start < mid ); X X return !lower_case; X} X X/* Remove an article from its parent/siblings. Leave parent pointer intact. X*/ Xvoid Xunlink_child( child ) Xregister ARTICLE *child; X{ X register ARTICLE *last; X X if( !(last = child->parent) ) { X child->root->thread_cnt--; X if( (last = child->root->articles) == child ) { X child->root->articles = child->siblings; X } else { X goto sibling_search; X } X } else { X last->child_cnt--; X if( last->children == child ) { X last->children = child->siblings; X } else { X last = last->children; X sibling_search: X while( last->siblings != child ) { X last = last->siblings; X } X last->siblings = child->siblings; X } X } X} X X/* Link an article to its parent article. If its parent pointer is zero, X** link it to its root. Sorts siblings by date. X*/ Xvoid Xlink_child( child ) Xregister ARTICLE *child; X{ X register ARTICLE *node; X register ROOT *root; X X if( !(node = child->parent) ) { X root = child->root; X root->thread_cnt++; X node = root->articles; X if( !node || child->date < node->date ) { X child->siblings = node; X root->articles = child; X } else { X goto sibling_search; X } X } else { X node->child_cnt++; X node = node->children; X if( !node || child->date < node->date ) { X child->siblings = node; X child->parent->children = child; X } else { X sibling_search: X for( ; node->siblings; node = node->siblings ) { X if( node->siblings->date > child->date ) { X break; X } X } X child->siblings = node->siblings; X node->siblings = child; X } X } X} X X/* Create a new root for the specified article. If the current subject_str X** matches any pre-existing root's subjects, we'll instead add it on as a X** parallel thread. X*/ Xvoid Xmake_root( article ) XARTICLE *article; X{ X register ROOT *new, *node; X register SUBJECT *subject; X X#ifndef NO_SUBJECT_MATCHING X /* First, check the other root's subjects for a match. */ X for( node = root_root; node; node = node->link ) { X for( subject = node->subjects; subject; subject = subject->link ) { X if( subject_equal( subject->str, subject_str ) ) { X use_root( article, node ); /* use it instead */ X link_child( article ); X return; /* RETURN */ X } X } X } X#endif X X /* Create a new root. */ X new = (ROOT *)safemalloc( sizeof (ROOT) ); X total.root++; X new->articles = article; X new->root_num = article->num; X new->thread_cnt = 1; X if( article->num ) { X article->author = new_author(); X new->subject_cnt = 1; X new->subjects = article->subject = new_subject(); X } else { X new->subject_cnt = 0; X new->subjects = Null(SUBJECT*); X } X article->root = new; X new->link = root_root; X root_root = new; X} X X/* Add this article's subject onto the indicated root's list. Point the X** article at the root. X*/ Xvoid Xuse_root( article, root ) XARTICLE *article; XROOT *root; X{ X register SUBJECT *subject; X register ROOT *root2; X SUBJECT *hold, *child_subj = Null(SUBJECT*); X ARTICLE *node; X X article->root = root; X X /* If it's a fake, there's no subject to add. */ X if( !article->num ) { X return; /* RETURN */ X } X X /* If we haven't picked a unique message number to represent this root, X ** use the first non-zero number we encounter. Which one doesn't matter. X */ X if( !root->root_num ) { X root->root_num = article->num; X } X article->author = new_author(); X X /* Check if the new subject matches any of the other subjects in this root. X ** If so, we just update the count. If not, check all the other roots for X ** a match. If found, the new subject is common between the two roots, so X ** we merge the two roots together. X */ X root2 = root; X#ifndef NO_SUBJECT_MATCHING X do { X#endif X for( subject = root2->subjects; subject; subject = subject->link ) { X if( subject_equal( subject->str, subject_str ) ) { X article->subject = subject; X subject->count++; X#ifndef NO_SUBJECT_MATCHING X if( root2 != root ) { X merge_roots( root, root2 ); X } X#endif X return; /* RETURN */ X } X } X#ifndef NO_SUBJECT_MATCHING X if( (root2 = root2->link) == Null(ROOT*) ) { X root2 = root_root; X } X } while( root2 != root ); X#endif X X article->subject = hold = new_subject(); X root->subject_cnt++; X X /* Find subject of any pre-existing children. We want to insert the new X ** subject before a child's to keep the subject numbering intuitive X ** in the newsreader. X */ X for( node = article->children; node; node = node->children ) { X if( node->subject ) { X child_subj = node->subject; X break; X } X } X if( !(subject = root->subjects) || subject == child_subj ) { X hold->link = root->subjects; X root->subjects = hold; X } else { X while( subject->link && subject->link != child_subj ) { X subject = subject->link; X } X hold->link = subject->link; X subject->link = hold; X } X} X X/* Check subjects in a case-insignificant, punctuation ignoring manner. X*/ Xint Xsubject_equal( str1, str2 ) Xregister char *str1, *str2; X{ X register char ch1, ch2; X X while( (ch1 = *str1++) ) { X if( ch1 == ' ' || ispunct( ch1 ) ) { X while( *str1 && (*str1 == ' ' || ispunct( *str1 )) ) { X str1++; X } X ch1 = ' '; X } else if( isupper( ch1 ) ) { X ch1 = tolower( ch1 ); X } X if( !(ch2 = *str2++) ) { X return 0; X } X if( ch2 == ' ' || ispunct( ch2 ) ) { X while( *str2 && (*str2 == ' ' || ispunct( *str2 )) ) { X str2++; X } X ch2 = ' '; X } else if( isupper( ch2 ) ) { X ch2 = tolower( ch2 ); X } X if( ch1 != ch2 ) { X return 0; X } X } X if( *str2 ) { X return 0; X } X return 1; X} X X/* Create a new subject structure. */ XSUBJECT * Xnew_subject() X{ X register int len = strlen( subject_str ) + 1; X register SUBJECT *subject; X X subject = (SUBJECT *)safemalloc( sizeof (SUBJECT) ); X total.subject++; X subject->count = 1; X subject->link = Null(SUBJECT*); X subject->str = safemalloc( len ); X total.string1 += len; X bcopy( subject_str, subject->str, len ); X X return subject; X} X X/* Create a new author structure. */ XAUTHOR * Xnew_author() X{ X register len = strlen( author_str ) + 1; X register AUTHOR *author, *last_author; X X last_author = Null(AUTHOR*); X for( author = author_root; author; author = author->link ) { X#ifndef DONT_COMPARE_AUTHORS /* might like to define this to save time */ X if( strEQ( author->name, author_str ) ) { X author->count++; X return author; /* RETURN */ X } X#endif X last_author = author; X } X X author = (AUTHOR *)safemalloc( sizeof (AUTHOR) ); X total.author++; X author->count = 1; X author->link = Null(AUTHOR*); X author->name = safemalloc( len ); X total.string1 += len; X bcopy( author_str, author->name, len ); X X if( last_author ) { X last_author->link = author; X } else { X author_root = author; X } X return author; X} X X/* Insert all of root2 into root1, setting the proper root values and X** updating subject counts. X*/ Xvoid Xmerge_roots( root1, root2 ) XROOT *root1, *root2; X{ X register ARTICLE *node, *next; X register SUBJECT *subject; X X /* Remember whoever's root num is lower. This could screw up a X ** newsreader's kill-thread code if someone already saw the roots as X ** being separate, but it must be done. The newsreader code will have X ** to handle this as best as it can. X */ X if( root1->root_num > root2->root_num ) { X root1->root_num = root2->root_num; X } X X for( node = root2->articles; node; node = next ) { X /* For each article attached to root2, detach them, set the X ** branch's root pointers to root1, and then attach it to root1. X */ X next = node->siblings; X unlink_child( node ); X node->siblings = Nullart; X set_root( node, root1 ); /* sets children too */ X /* Link_child() depends on node->parent being null and node->root X ** being set. X */ X link_child( node ); X } X root1->subject_cnt += root2->subject_cnt; X if( !(subject = root1->subjects) ) { X root1->subjects = root2->subjects; X } else { X while( subject->link ) { X subject = subject->link; X } X subject->link = root2->subjects; X } X unlink_root( root2 ); X free_root( root2 ); X} X X/* When merging roots, we need to reset all the root pointers. X*/ Xvoid Xset_root( node, root ) XARTICLE *node; XROOT *root; X{ X do { X node->root = root; X if( node->children ) { X set_root( node->children, root ); X } X } while( node = node->siblings ); X} X X/* Unlink a root from its neighbors. */ Xvoid Xunlink_root( root ) Xregister ROOT *root; X{ X register ROOT *node; X X if( (node = root_root) == root ) { X root_root = root->link; X } else { X while( node->link != root ) { X node = node->link; X } X node->link = root->link; X } X} X X/* Free an article and its message-id string. All other resources must X** already be free, and it must not be attached to any threads. X*/ Xvoid Xfree_article( this ) XARTICLE *this; X{ X register ARTICLE *art; X X if( (art = this->domain->ids) == this ) { X if( !(this->domain->ids = this->id_link) ) { X free_domain( this->domain ); X } X } else { X while( this != art->id_link ) { X art = art->id_link; X } X art->id_link = this->id_link; X } X total.string2 -= strlen( this->id ) + 1; X free( this->id ); X free( this ); X total.article--; X} X X/* Free the domain only when its last unique id has been freed. */ Xvoid Xfree_domain( this ) XDOMAIN *this; X{ X register DOMAIN *domain; X X if( this == (domain = &unk_domain) ) { X return; X } X if( this == next_domain ) { /* help expire routine skip freed domains */ X next_domain = next_domain->link; X } X while( this != domain->link ) { X domain = domain->link; X } X domain->link = this->link; X total.string2 -= strlen( this->name ) + 1; X free( this->name ); X free( this ); X total.domain--; X} X X/* Free the subject structure and its string. */ Xvoid Xfree_subject( this ) XSUBJECT *this; X{ X total.string1 -= strlen( this->str ) + 1; X free( this->str ); X free( this ); X total.subject--; X} X X/* Free a root. It must already be unlinked. */ Xvoid Xfree_root( this ) XROOT *this; X{ X free( this ); X total.root--; X} X X/* Free the author structure when it's not needed any more. */ Xvoid Xfree_author( this ) XAUTHOR *this; X{ X total.string1 -= strlen( this->name ) + 1; X free( this->name ); X free( this ); X total.author--; X} END_OF_FILE if test 34673 -ne `wc -c <'mt-process.c'`; then echo shar: \"'mt-process.c'\" unpacked with wrong size! fi # end of 'mt-process.c' fi if test -f 'rt-select.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'rt-select.c'\" else echo shar: Extracting \"'rt-select.c'\" \(22272 characters\) sed "s/^X//" >'rt-select.c' <<'END_OF_FILE' X/* $Header: rt-select.c,v 4.3.3.2 90/08/20 18:32:33 davison Trn $ X** X** $Log: rt-select.c,v $ X** Revision 4.3.3.2 90/08/20 18:32:33 davison X** Reset scan_all_roots while selecting. X** X** Revision 4.3.3.1 90/07/24 22:04:07 davison X** Initial Trn Release X** X*/ X X#include "EXTERN.h" X#include "common.h" X#include "rn.h" X#include "rcstuff.h" X#include "term.h" X#include "final.h" X#include "util.h" X#include "help.h" X#include "bits.h" X#include "artsrch.h" X#include "ng.h" X#include "ngstuff.h" X#include "rthreads.h" X X#ifdef USETHREADS X Xstatic int count_subj_lines(); Xstatic void display_subj(); X X/* When display mode is 'l', each author gets a separate line; when 'm', up to X** three authors share a line; when 's', no authors are displayed. X*/ Xstatic char *display_mode = select_order; Xstatic ART_NUM article_count; Xstatic int author_line; Xstatic char first_two_chars[3] = { ' ', ' ', '\0' }, mask = 1; X X#define MAX_SEL 64 X X/* Display a menu of roots for the user to choose from. If cmd is '+' X** we display all the unread roots and allow the user to mark roots as X** selected and perform various commands upon the articles. If cmd is X** 'U' we display all the previously read roots and allow the user to X** select which ones should be marked as unread. X*/ Xchar Xselect_thread( cmd ) Xchar cmd; X{ X register int i, j, cnt; X ART_NUM art_hold = art; X int line_cnt, screen_line, subj_line_cnt; X int cur_root, page_root, last_root = -1; X ART_LINE running_total, last_running; X int last_line, got_dash; X int max_root; X int first, last; X int root_line[MAX_SEL], root_hold[MAX_SEL]; X int ch, action; X char page_char, end_char; X char promptbuf[80]; X bool etc, clean_screen, empty_ok, displayed_status; X char oldmode = mode; X#ifndef CONDSUB X char tmpbuf[2]; X#endif X char *select_chars, *in_select; X int max_cnt; X X mode = 't'; X unread_selector = (cmd == 'U'); X clear_on_stop = TRUE; X empty_ok = FALSE; X X select_threads: X /* Setup for selecting articles to read or set unread */ X scan_all_roots = FALSE; X if( unread_selector ) { X page_char = '>'; X end_char = 'Z'; X page_root = 0; X last_root = -1; X cmd = 0; X } else { X page_char = page_select; X end_char = end_select; X page_root = select_page; X if( curr_p_art ) { X last_root = curr_p_art->root; X } X } X mask = unread_selector+1; X X /* Leave empty roots selected for a short time to give them a chance X ** to 'q' out of the selector if they got here by mistake. X */ X max_root = count_roots( FALSE ); X X /* If nothing to display, we're done. */ X if( !article_count && !empty_ok ) { X all_empty: X clear_on_stop = FALSE; X mode = oldmode; X putchar( '\n' ); X if( unread_selector ) { X#ifdef VERBOSE X IF(verbose) X fputs( "\nNo articles to set unread.\n", stdout ); X ELSE X#endif X#ifdef TERSE X fputs( "\nNo articles.\n", stdout ) FLUSH; X#endif X unread_selector = 0; X mask = 1; X } else { X#ifdef VERBOSE X IF(verbose) X fputs( "\nNo unread articles to select.\n", stdout ); X ELSE X#endif X#ifdef TERSE X fputs( "\nNo articles.\n", stdout ); /* let "them" FLUSH */ X#endif X } X (void) count_roots( TRUE ); X art = art_hold; X p_art = curr_p_art; X return 'q'; X } X if( unread_selector ) { X for( j = 0; j < total.root; j++ ) { X selected_roots[j] |= 4; X } X } X if( page_root >= max_root ) { X ch = '<'; X } else { X ch = '>'; X } X cur_root = 0; X running_total = 0; X for( i = 0; i < page_root; i++ ) { X running_total += root_article_cnts[i]; X } X do { X select_chars = getval( "SELECTCHARS", SELECTCHARS ); X max_cnt = strlen( select_chars ); X if( max_cnt > MAX_SEL ) { X max_cnt = MAX_SEL; X } X if( ch == '<' && i ) { X screen_line = 2; X cnt = 0; X /* Scan the roots in reverse to go back a page */ X do { X if( !root_article_cnts[--i] ) { X continue; X } X first = root_subjects[i]; X last = first + p_roots[i].subject_cnt; X line_cnt = 0; X for( j = first; j < last; j++ ) { X line_cnt += count_subj_lines( i, j ); X } X if( line_cnt > LINES - 5 ) { X line_cnt = LINES - 5; X } X screen_line += line_cnt; X if( screen_line > LINES - 3 ) { X i++; X break; X } X running_total -= root_article_cnts[i]; X cnt++; X } while( i > 0 && cnt < max_cnt ); X } X X /* Present a page of subjects to the user */ X#ifndef CLEAREOL X clear(); X#else X if( can_home_clear ) { X home_cursor(); X maybe_eol(); X } else { X clear(); X } X#endif X carriage_return(); X page_root = i; X last_running = running_total; X#ifdef NOFIREWORKS X no_sofire(); X#endif X standout(); X fputs( ngname, stdout ); X un_standout(); X printf( "\t\t\t\t%ld %sarticle%s\n", (long)article_count, X unread_selector? "read " : nullstr, X article_count == 1 ? nullstr : "s" ); X#ifdef CLEAREOL X maybe_eol(); X#endif X putchar( '\n' ) FLUSH; X screen_line = 2; X for( cnt = 0; i < max_root && cnt < max_cnt; i++ ) { X if( last_root == i ) { X cur_root = cnt; X } X /* Check each root for articles to list */ X if( !root_article_cnts[i] ) { X continue; X } X first = root_subjects[i]; X last = first + p_roots[i].subject_cnt; X X /* Compute how many lines we need to display the subjects/authors */ X etc = FALSE; X line_cnt = 0; X for( j = first; j < last; j++ ) { X subj_line_cnt = count_subj_lines( i, j ); X line_cnt += subj_line_cnt; X /* If this root is too long to fit on the screen all by X ** itself, trim it to fit and set the "etc" flag. X */ X if( line_cnt > LINES - 5 ) { X last = j; X line_cnt -= subj_line_cnt; X if( line_cnt != LINES - 5 ) { X last++; X line_cnt = LINES - 5; X } X if( screen_line == 2 ) { X etc = TRUE; X } X break; X } X } X /* If it doesn't fit, save it for the next page */ X if( screen_line + line_cnt > LINES - 3 ) { X break; X } X /* Output the subjects, with optional authors */ X root_line[cnt] = screen_line; X running_total += root_article_cnts[i]; X first_two_chars[0] = select_chars[cnt]; X first_two_chars[1] = (selected_roots[i] & 4) ? '-' : X (selected_roots[i] & mask) ? '+' : ' '; X author_line = screen_line; X for( j = first; j < last; j++ ) { X display_subj( i, j ); X } X screen_line += line_cnt; X root_hold[cnt++] = i; X if( etc ) { X fputs( " ...etc.", stdout ); X i++; X break; X } X }/* for */ X last_root = -1; X if( cur_root && cur_root >= cnt ) { X cur_root = cnt - 1; X } X X /* Check if there is really anything left to display. */ X if( !running_total && !empty_ok ) { X goto all_empty; X } X empty_ok = FALSE; X X last_line = screen_line+1; X#ifdef CLEAREOL X maybe_eol(); X#endif X putchar( '\n' ) FLUSH; X /* Prompt the user */ X strcpy( promptbuf, "-- Select threads -- " ); X if( i != max_root ) { X sprintf( promptbuf+21, "%s%d%% [%c%c] --", X (!page_root? "Top " : nullstr), X running_total*100 / article_count, page_char, end_char ); X } else { X sprintf( promptbuf+21, "%s [%c%c] --", X (!page_root? "All" : "Bot"), end_char, page_char ); X } X if( cur_root > cnt ) { X cur_root = 0; X } X screen_line = root_line[cur_root]; X#ifdef CLEAREOL X if( erase_screen && can_home_clear ) { X clear_rest(); X } X#endif X displayed_status = FALSE; X prompt_select: X standout(); X fputs( promptbuf, stdout ); X un_standout(); X if( can_home ) { X carriage_return(); X goto_line( last_line, screen_line ); X } X got_dash = 0; X /* Grab some commands from the user */ X for( ;; ) { X fflush(stdout); X eat_typeahead(); X#ifdef CONDSUB X getcmd( buf ); X ch = *buf; X#else X getcmd( tmpbuf ); /* If no conditionals, don't allow macros */ X ch = *tmpbuf; X buf[0] = ch; X buf[1] = FINISHCMD; X#endif X in_select = index( select_chars, ch ); X /* Plaster any inherited empty roots on first command, if not q. */ X if( cmd && (in_select || (ch != '\033' && ch != 'q')) ) { X max_root = count_roots( TRUE ); X cmd = 0; X } X if( displayed_status && can_home ) { X goto_line( screen_line, last_line+1 ); X erase_eol(); X screen_line = last_line+1; X displayed_status = FALSE; X } X if( ch == '-' ) { X got_dash = 1; X if( !can_home ) { X putchar( '-' ); X fflush( stdout ); X } X continue; X } X if( ch == ' ' ) { X if( i == max_root ) { X ch = end_char; X } else { X ch = page_char; X } X } X if( !in_select && (index( "<>^$!?&:/hDJLNqQUXZ\n\r\t\033", ch ) X || ch == Ctl('l') || ch == Ctl('r') || ch == Ctl('k')) ) { X break; X } X if( in_select ) { X j = in_select - select_chars; X if( j >= cnt ) { X dingaling(); X j = -1; X } else if( got_dash ) { X ; X } else if( selected_roots[root_hold[j]] & mask ) { X action = (unread_selector ? 'k' : '-'); X } else { X action = '+'; X } X } else if( ch == 'y' || ch == '.' ) { X j = cur_root; X if( selected_roots[root_hold[j]] & mask ) { X action = (unread_selector ? 'k' : '-'); X } else { X action = '+'; X } X } else if( ch == 'k' || ch == 'j' || ch == ',' ) { X j = cur_root; X action = 'k'; X } else if( ch == 'm' || ch == '\\' ) { X j = cur_root; X action = 'm'; X } else if( ch == '@' ) { X cur_root = 0; X j = cnt-1; X got_dash = 1; X action = '@'; X } else if( ch == '[' || ch == 'p' ) { X if( --cur_root < 0 ) { X cur_root = cnt ? cnt-1 : 0; X } X j = -1; X } else if( ch == ']' || ch == 'n' ) { X if( ++cur_root >= cnt ) { X cur_root = 0; X } X j = -1; X } else { X if( can_home ) { X goto_line( screen_line, last_line+1 ); X screen_line = last_line+1; X } else { X putchar( '\n' ); X } X printf( "Type ? for help." ); X settle_down(); X displayed_status = TRUE; X X if( can_home ) { X carriage_return(); X } else { X putchar( '\n' ); X } X j = -1; X } X if( j >= 0 ) { X if( !got_dash ) { X cur_root = j; X } else { X got_dash = 0; X if( j < cur_root ) { X ch = cur_root-1; X cur_root = j; X j = ch; X } X } X if( ++j == cnt ) { X j = 0; X } X do { X if( can_home ) { X goto_line( screen_line, root_line[cur_root] ); X screen_line = root_line[cur_root]; X } X putchar( select_chars[cur_root] ); X if( action == '@' ) { X if( selected_roots[root_hold[cur_root]] & 4 ) { X ch = (unread_selector ? '+' : ' '); X } else if( unread_selector ) { X ch = 'k'; X } else X if( selected_roots[root_hold[cur_root]] & mask ) { X ch = '-'; X } else { X ch = '+'; X } X } else { X ch = action; X } X switch( ch ) { X case '+': X if( !(selected_roots[root_hold[cur_root]] & mask) ) { X selected_roots[root_hold[cur_root]] |= mask; X selected_root_cnt++; X selected_count X += root_article_cnts[root_hold[cur_root]]; X putchar( '+' ); X } X /* FALL THROUGH */ X case 'm': X if( selected_roots[root_hold[cur_root]] & 4 ) { X selected_roots[root_hold[cur_root]] &= ~4; X if( ch == 'm' ) { X putchar( ' ' ); X } X } else if( ch == 'm' ) { X goto unsel; X } X break; X case 'k': X if( !(selected_roots[root_hold[cur_root]] & 4) ) { X selected_roots[root_hold[cur_root]] |= 4; X putchar( '-' ); X } X /* FALL THROUGH */ X case '-': X unsel: X if( selected_roots[root_hold[cur_root]] & mask ) { X selected_roots[root_hold[cur_root]] &= ~mask; X selected_root_cnt--; X selected_count X -= root_article_cnts[root_hold[cur_root]]; X if( ch != 'k' ) { X putchar( ' ' ); X } X } X break; X } X fflush( stdout ); X if( ++cur_root == cnt ) { X cur_root = 0; X } X if( can_home ) { X carriage_return(); X } X } while( cur_root != j ); X } else { X got_dash = FALSE; X } X if( can_home ) { X goto_line( screen_line, root_line[cur_root] ); X screen_line = root_line[cur_root]; X } X }/* for */ X if( can_home) { X goto_line( screen_line, last_line ); X } X clean_screen = TRUE; X do_command: X if( ch == 'L' ) { X if( !*++display_mode ) { X display_mode = select_order; X } X ch = Ctl('l'); X cur_root = 0; X } else if( ch == '$' ) { X ch = '<'; X page_root = max_root; X last_running = article_count; X cur_root = 0; X } else if( ch == '^' || ch == Ctl('r') ) { X ch = '>'; X i = 0; X running_total = 0; X cur_root = 0; X } else if( ch == 'h' || ch == '?' ) { X putchar( '\n' ); X if( (ch = help_select()) || (ch = pause_getcmd()) ) { X goto got_cmd; X } X ch = Ctl('l'); X } else if( index( ":/&!", ch ) ) { X erase_eol(); /* erase the prompt */ X if( !finish_command( TRUE ) ) { /* get rest of command */ X if( clean_screen ) { X screen_line = root_line[cur_root]; X goto prompt_select; X } X goto extend_done; X } X if( ch == '&' || ch == '!' ) { X one_command = TRUE; X perform( buf, FALSE ); X one_command = FALSE; X X putchar( '\n' ) FLUSH; X clean_screen = FALSE; X } else { X int selected_save = selected_root_cnt; X X if( ch == ':' ) { X clean_screen = (use_selected() == 2) && clean_screen; X } else { X /* Force the search to begin at absfirst or firstart, X ** depending upon whether they specified the 'r' option. X */ X art = lastart+1; X page_line = 1; X switch( art_search( buf, sizeof buf, FALSE ) ) { X case SRCH_ERROR: X case SRCH_ABORT: X case SRCH_INTR: X fputs( "\nInterrupted\n", stdout ) FLUSH; X break; X case SRCH_DONE: X case SRCH_SUBJDONE: X fputs( "Done\n", stdout ) FLUSH; X break; X case SRCH_NOTFOUND: X fputs( "\nNot found.\n", stdout ) FLUSH; X break; X case SRCH_FOUND: X break; X } X clean_screen = FALSE; X } X /* Recount, in case something has changed. */ X max_root = count_roots( !unread_selector ); X X if( (selected_save -= selected_root_cnt) != 0 ) { X putchar( '\n' ); X if( selected_save < 0 ) { X fputs( "S", stdout ); X selected_save *= -1; X } else { X fputs( "Des", stdout ); X } X printf( "elected %d thread%s.", selected_save, X selected_save == 1 ? nullstr : "s" ); X clean_screen = FALSE; X } X if( !clean_screen ) { X putchar('\n') FLUSH; X } X }/* if !& or :/ */ X X if( clean_screen ) { X carriage_return(); X up_line(); X erase_eol(); X screen_line = root_line[cur_root]; X goto prompt_select; X } X extend_done: X if( (ch = pause_getcmd()) ) { X got_cmd: X if( ch > 0 ) { X /* try to optimize the screen update for some commands. */ X if( !index( select_chars, ch ) X && (index( "<>^$!?&:/hDJLNqQUXZ\n\r\t\033", ch ) X || ch == Ctl('k')) ) { X buf[0] = ch; X buf[1] = FINISHCMD; X goto do_command; X } X pushchar( ch | 0200 ); X } X } X ch = Ctl('l'); X } else if( ch == Ctl('k') ) { X edit_kfile(); X ch = Ctl('l'); X } else if( !unread_selector && (ch == 'X' || ch == 'D' || ch == 'J') ) { X if( ch == 'D' ) { X j = page_root; X last = i; X } else { X j = 0; X last = max_root; X } X for( ; j < last; j++ ) { X if( (!(selected_roots[j] & 1) ^ (ch == 'J')) X && (cnt = root_article_cnts[j]) ) { X p_art = p_articles + p_roots[j].articles; X art = 0; X follow_thread( 'J' ); X } X } X max_root = count_roots( TRUE ); X if( article_count X && (ch == 'J' || (ch == 'D' && !selected_root_cnt)) ) { X ch = Ctl('l'); X cur_root = 0; X } else { X break; X } X } else if( ch == 'J' ) { X for( j = 0; j < max_root; j++ ) { X selected_roots[j] = (selected_roots[j] & ~2) | 4; X } X selected_root_cnt = selected_count = 0; X ch = Ctl('l'); X } X if( ch == '>' ) { X cur_root = 0; X } else if( ch == '<' || (page_root && page_root >= max_root) ) { X cur_root = 0; X running_total = last_running; X if( !(i = page_root) || !max_root ) { X ch = '>'; X } else { X ch = '<'; X } X } else if( ch == Ctl('l') ) { X i = page_root; X running_total = last_running; X ch = '>'; X } else if( ch == '\r' || ch == '\n' ) { X if( !selected_root_cnt ) { X selected_roots[root_hold[cur_root]] = mask; X selected_root_cnt++; X selected_count += root_article_cnts[root_hold[cur_root]]; X } X } X } while( (ch == '>' && i < max_root) || ch == '<' ); X putchar( '\n' ) FLUSH; X X if( unread_selector ) { X /* Turn selections into unread selected roots. Let count_roots() X ** fix the counts after we're through. X */ X last_root = -1; X for( j = 0; j < total.root; j++ ) { X if( !(selected_roots[j] & 4) ) { X if( selected_roots[j] & 2 ) { X selected_roots[j] = 1; X } X p_art = p_articles + p_roots[j].articles; X art = 0; X follow_thread( 'u' ); X } else { X selected_roots[j] &= ~4; X } X } X } else { X select_page = page_root; X for( j = 0; j < total.root; j++ ) { X if( selected_roots[j] & 4 ) { X selected_roots[j] = 0; X p_art = p_articles + p_roots[j].articles; X art = 0; X follow_thread( 'J' ); X } X } X } X if( ch == 'U' ) { X unread_selector = !unread_selector; X empty_ok = TRUE; X goto select_threads; X } X X if( unread_selector ) { X unread_selector = 0; X mask = 1; X (void) count_roots( FALSE ); X } X if( ch == '\033' || Ctl(ch) == Ctl('q') ) { X ch = (ch == 'Q' ? 'Q' : 'q'); X art = art_hold; X p_art = curr_p_art; X } else if( ch == 'N' ) { X art = art_hold; X p_art = curr_p_art; X } else { X first_art(); X } X clear_on_stop = FALSE; X mode = oldmode; X return ch; X} X Xstatic int author_cnt, first_author; X X/* Counts the number of lines needed to output a subject, including optional X** authors. X*/ Xstatic int Xcount_subj_lines( root, subj ) Xint root; Xint subj; X{ X PACKED_ARTICLE *artp, *root_limit; X int author_subj; X X author_cnt = 0; X author_subj = subj; X first_author = -1; X X if( !subject_cnts[subj] ) { X return 0; X } X if( *display_mode == 's' ) { /* no-author mode takes one line */ X return ++author_cnt; X } X bzero( author_cnts, total.author * sizeof (WORD) ); X X /* Count authors associated with this subject. Increments author_cnts. */ X artp = p_articles + p_roots[root].articles; X root_limit = upper_limit( artp, FALSE ); X for( ; artp != root_limit; artp++ ) { X if( artp->subject == author_subj X && (!was_read( artp->num ) ^ unread_selector) ) { X if( artp->author < 0 || artp->author >= total.author ) { X printf( "\ XFound invalid author (%d) with valid subject (%d)! [%ld]\n", X artp->author, artp->subject, artp->num ); X artp->author = 0; X } else { X if( first_author < 0 ) { X first_author = artp->author; X } X if( !author_cnts[artp->author]++ ) { X author_cnt++; X } X } X } X } X X if( *display_mode == 'm' ) { X return (author_cnt+4)/3; X } else { X return author_cnt; X } X} X Xstatic void Xdisplay_subj( root, subj ) Xint root; Xint subj; X{ X PACKED_ARTICLE *artp, *root_limit; X char *str; X X count_subj_lines( root, subj ); X if( !author_cnt ) { X return; X } X artp = p_articles + p_roots[root].articles; X if( artp->subject != -1 && (artp->flags & ROOT_ARTICLE) X && (!was_read(artp->num) ^ unread_selector) ) { X str = nullstr; X } else { X str = ">"; X } X#ifdef CLEAREOL X maybe_eol(); X#endif X if( *display_mode == 's' ) { X printf( "%s%3d %s%.71s\n", first_two_chars, X subject_cnts[subj], str, subject_ptrs[subj] ) FLUSH; X } else { X printf( "%s%-16.16s%3d %s%.55s", first_two_chars, X author_ptrs[first_author], X subject_cnts[subj], str, subject_ptrs[subj] ); X if( author_cnt > 1 ) { X author_cnts[first_author] = 0; X author_cnt = 0; X root_limit = upper_limit( artp, FALSE ); X for( ; artp != root_limit; artp++ ) { X if( artp->author >= 0 && author_cnts[artp->author] ) { X switch( author_cnt % 3 ) { X case 0: X putchar( '\n' ) FLUSH; X if( ++author_line >= LINES - 3 ) { X return; X } X#ifdef CLEAREOL X maybe_eol(); X#endif X putchar( ' ' ); X putchar( ' ' ); X break; X case 1: X putchar( '\t' ); X putchar( '\t' ); X break; X case 2: X putchar( '\t' ); X break; X } X author_cnt += (*display_mode == 'm'); X printf( "%-16.16s", author_ptrs[artp->author] ); X author_cnts[artp->author] = 0; X }/* if */ X }/* for */ X }/* if */ X putchar( '\n' ) FLUSH; X author_line++; X }/* if */ X first_two_chars[0] = first_two_chars[1] = ' '; X} X X/* Get each root's article count, and subject count(s); count total X** articles and selected articles (use unread_selector to determine X** whether to count read or unread articles); deselect any roots we X** find that are empty (if do_unselect is TRUE); find the last non- X** empty root, and return its count (the index+1). X*/ Xint Xcount_roots( do_unselect ) Xbool do_unselect; X{ X register int count; X register PACKED_ARTICLE *artp, *root_limit, *art_limit; X int last_root = -1; X X article_count = selected_count = selected_root_cnt = 0; X X if( !(artp = p_articles) ) { X return 0; X } X art_limit = artp + total.article; X root_limit = upper_limit( artp, 0 ); X X bzero( subject_cnts, total.subject * sizeof (WORD) ); X count = 0; X X for( ;; ) { X if( artp->subject == -1 ) { X if( !was_read( artp->num ) ) { X oneless( artp->num ); X } X } else if( (!was_read( artp->num ) ^ unread_selector) ) { X count++; X subject_cnts[artp->subject]++; X } X if( ++artp == root_limit ) { X int root_num = artp[-1].root; X X root_article_cnts[root_num] = count; X if( count ) { X article_count += count; X if( selected_roots[root_num] & mask ) { X selected_roots[root_num] &= ~4; X selected_root_cnt++; X selected_count += count; X } X last_root = root_num; X } else if( do_unselect ) { X selected_roots[root_num] &= ~mask; X } else if( selected_roots[root_num] & mask ) { X selected_roots[root_num] &= ~4; X selected_root_cnt++; X } X if( artp == art_limit ) { X break; X } X root_limit = upper_limit( artp, 0 ); X count = 0; X } X } X if( do_unselect ) { X scan_all_roots = !article_count; X } X unthreaded = toread[ng] - article_count; X X return last_root+1; X} X X/* Count the unread articles attached to the given root number. X*/ Xint Xcount_one_root( root_num ) Xint root_num; X{ X int last = (root_num == total.root-1 ? total.article X : p_roots[root_num+1].articles); X register int count = 0, i; X X for( i = p_roots[root_num].articles; i < last; i++ ) { X if( p_articles[i].subject != -1 && !was_read( p_articles[i].num ) ) { X count++; X } X } X root_article_cnts[root_num] = count; X X return count; X} X X#endif /* USETHREADS */ END_OF_FILE if test 22272 -ne `wc -c <'rt-select.c'`; then echo shar: \"'rt-select.c'\" unpacked with wrong size! fi # end of 'rt-select.c' fi if test -f 'threads.h' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'threads.h'\" else echo shar: Extracting \"'threads.h'\" \(1989 characters\) sed "s/^X//" >'threads.h' <<'END_OF_FILE' X/* $Header: threads.h,v 4.3.3.2 90/08/20 16:50:09 davison Trn $ X** X** $Log: threads.h,v $ X** Revision 4.3.3.2 90/08/20 16:50:09 davison X** Padded odd-boundaried arrays. Upgraded database version #. X** X** Revision 4.3.3.1 90/06/20 22:56:18 davison X** Initial Trn Release X** X*/ X X#define DB_VERSION 2 X Xtypedef char BYTE; Xtypedef short WORD; Xtypedef long LONG; X X#define ROOT_ARTICLE 0x0001 /* article flag definitions */ X#define NEW_ARTICLE 0x0002 /* to avoid stat'ing new articles */ X Xtypedef struct Article { X ART_NUM num; X char *id; X struct Domain *domain; X struct Subject *subject; X struct Author *author; X struct Article *parent, *children, *siblings; X struct Root *root; X struct Article *id_link; X time_t date; X WORD child_cnt; X WORD flags; X WORD seq; X} ARTICLE; X Xtypedef struct Domain { X char *name; X ARTICLE *ids; X struct Domain *link; X} DOMAIN; X Xtypedef struct Author { X struct Author *link; /* this link MUST be first */ X char *name; X WORD seq; X WORD count; X} AUTHOR; X Xtypedef struct Subject { X struct Subject *link; X char *str; X WORD seq; X WORD count; X} SUBJECT; X Xtypedef struct Root { X struct Root *link; /* this link MUST be first */ X ARTICLE *articles; X SUBJECT *subjects; X ART_NUM root_num; X WORD thread_cnt; X WORD subject_cnt; X WORD seq; X} ROOT; X Xtypedef struct { X LONG root_num; X WORD articles; X WORD thread_cnt; X WORD subject_cnt; X WORD pad_hack; X} PACKED_ROOT; X Xtypedef struct { X LONG num; X LONG date; X WORD subject, author; X WORD flags; X WORD child_cnt; X WORD parent, children, siblings; X WORD root; X} PACKED_ARTICLE; X Xtypedef struct Total { X LONG first, last; X LONG string1; X LONG string2; X WORD root; X WORD article; X WORD subject; X WORD author; X WORD domain; X WORD pad_hack; X} TOTAL; X Xtypedef struct { X BYTE l[sizeof (LONG)]; X BYTE w[sizeof (WORD)]; X BYTE version; X BYTE pad_hack; X} BMAP; END_OF_FILE if test 1989 -ne `wc -c <'threads.h'`; then echo shar: \"'threads.h'\" unpacked with wrong size! fi # end of 'threads.h' fi echo shar: End of archive 4 \(of 14\). cp /dev/null ark4isdone MISSING="" for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 14 archives. rm -f ark[1-9]isdone ark[1-9][0-9]isdone else echo You still must unpack the following archives: echo " " ${MISSING} fi exit 0 exit 0 # Just in case... -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net. Use a domain-based address or give alternate paths, or you may lose out.