Article 2701 of comp.sources.unix:
Path: nntpd.lkg.dec.com!crl.dec.com!crl.dec.com!bloom-beacon.mit.edu!news.kei.com!news.mathworks.com!newsfeed.internetmci.com!in1.uu.net!news1.digital.com!vixie!vixie!not-for-mail
From: davison@borland.com (Wayne Davison)
Newsgroups: comp.sources.unix
Subject: v29i043: trn-3.6 - threaded newsreader based on RN, V3.6, Part06/14
Date: 29 Oct 1995 01:32:52 -0800
Organization: Vixie Enterprises
Lines: 3646
Sender: vixie@vix.com
Approved: vixie@gw.home.vix.com
Message-ID: <46vho4$t4n@gw.home.vix.com>
References: <1.814959141.29825@gw.home.vix.com>
NNTP-Posting-Host: gw.home.vix.com

Submitted-By: davison@borland.com (Wayne Davison)
Posting-Number: Volume 29, Issue 43
Archive-Name: trn-3.6/part06

#!/bin/sh
# This is `part06' (part 6 of a multipart archive).
# Do not concatenate these parts, unpack them in order with `/bin/sh'.
# File `trn-3.6/ngdata.c' is being continued...
#
touch -am 1231235999 $$.touch >/dev/null 2>&1
if test ! -f 1231235999 && test -f $$.touch; then
  shar_touch=touch
else
  shar_touch=:
  echo
  echo 'WARNING: not restoring timestamps.  Consider getting and'
  echo "installing GNU \`touch', distributed in GNU File Utilities..."
  echo
fi
rm -f 1231235999 $$.touch
#
if test ! -r _sharseq.tmp; then
  echo 'Please unpack part 1 first!'
  exit 1
fi
shar_sequence=`cat _sharseq.tmp`
if test "$shar_sequence" != 6; then
  echo "Please unpack part $shar_sequence next!"
  exit 1
fi
if test ! -f _sharnew.tmp; then
  echo 'x - still skipping trn-3.6/ngdata.c'
else
  echo 'x - continuing file trn-3.6/ngdata.c'
  sed 's/^X//' << 'SHAR_EOF' >> 'trn-3.6/ngdata.c' &&
X#include "bits.h"
X#include "head.h"
X#include "rthread.h"
X#include "rt-select.h"
X#include "ng.h"
X#include "intrp.h"
X#include "kfile.h"
X#include "final.h"
X#include "rcln.h"
X#include "term.h"
X#include "util.h"
X#include "nntp.h"
X#include "ndir.h"
X#include "INTERN.h"
X#include "ngdata.h"
X
X/* open the active file */
X
Xvoid
Xngdata_init()
X{
X#ifdef USE_NNTP
X    switch (nntp_list("active", "control", 7)) {
X    case 1:
X	do {
X	    nntp_gets(ser_line, sizeof ser_line);
X	} while (!NNTP_LIST_END(ser_line));
X	/* FALL THROUGH */
X    case 0:
X	/*actfp = NULL;*/
X	fuzzyGet = 0;
X	break;
X    case -1:
X#endif
X	ngdatahash_init();
X	if (fseek(actfp,0L,0) == -1) {	/* just get to the beginning */
X	    printf("Error seeking in active file.\n") FLUSH;
X	    finalize(1);
X	}
X#ifdef USE_NNTP
X	break;
X    }
X#endif
X}
X
Xbool
Xaccess_ng()
X{
X#ifdef USE_NNTP
X    ART_NUM old_first = abs1st[ng];
X
X    if (!nntp_group(ngname,ng)) {
X	toread[ng] = TR_BOGUS;
X	return FALSE;
X    }
X    if ((lastart = getngsize(ng)) < 0)	/* this cannot happen (laugh here) */
X	return FALSE;
X    absfirst = abs1st[ng];
X    lastart = ngmax[ng];
X    if (absfirst > old_first)
X	checkexpired(ng,absfirst);
X#else /* !USE_NNTP */
X
X    if (eaccess(ngdir,5)) {		/* directory read protected? */
X	if (eaccess(ngdir,0)) {
X# ifdef VERBOSE
X	    IF(verbose)
X		printf("\nNewsgroup %s does not have a spool directory!\n",
X		    ngname) FLUSH;
X	    ELSE
X# endif
X# ifdef TERSE
X		printf("\nNo spool for %s!\n",ngname) FLUSH;
X# endif
X	} else {
X# ifdef VERBOSE
X	    IF(verbose)
X		printf("\nNewsgroup %s is not currently accessible.\n",
X		    ngname) FLUSH;
X	    ELSE
X# endif
X# ifdef TERSE
X		printf("\n%s not readable.\n",ngname) FLUSH;
X# endif
X	}
X	toread[ng] = TR_NONE; /* make this newsgroup temporarily invisible */
X	return FALSE;
X    }
X
X    /* chdir to newsgroup subdirectory */
X
X    if (chdir(ngdir)) {
X	printf(nocd,ngdir) FLUSH;
X	return FALSE;
X    }
X    if ((lastart = getngsize(ng)) < 0)	/* this cannot happen (laugh here) */
X	return FALSE;
X    absfirst = abs1st[ng];
X    setfoundbits();			/* might reset absfirst */
X#endif /* !USE_NNTP */
X
X    dmcount = 0;
X    in_ng = TRUE;			/* tell the world we are here */
X
X    build_cache();
X    return TRUE;
X}
X
Xvoid
Xgrow_ng(newlast)
XART_NUM newlast;
X{
X    ART_NUM tmpfirst;
X
X    forcegrow = FALSE;
X    if (newlast > lastart) {
X	ART_NUM tmpart = art;
X	toread[ng] += (ART_UNREAD)(newlast-lastart);
X	grow_cache(newlast);
X	tmpfirst = lastart+1;
X	lastart = newlast;
X	thread_grow();
X#ifdef KILLFILES
X#ifdef VERBOSE
X	IF(verbose)
X	    sprintf(buf,
X		"%ld more article%s arrived -- processing memorized commands...\n\n",
X		(long)(lastart - tmpfirst + 1),
X		(lastart > tmpfirst ? "s have" : " has" ) );
X	ELSE			/* my, my, how clever we are */
X#endif
X#ifdef TERSE
X	    strcpy(buf, "More news -- auto-processing...\n\n");
X#endif
X	if (has_normal_kills) {
X	    bool forcelast_save = forcelast;
X	    ARTICLE *artp_save = artp;
X	    kill_unwanted(tmpfirst,buf,TRUE);
X	    artp = artp_save;
X	    forcelast = forcelast_save;
X	}
X#endif
X	art = tmpart;
X    }
X}
X
Xvoid
Xng_skip()
X{
X#ifndef USE_NNTP			/* never read it & cannot find it? */
X    if (errno != ENOENT) {	/* has it not been deleted? */
X	clear();
X# ifdef VERBOSE
X	IF(verbose)
X	    printf("\n(Article %ld exists but is unreadable.)\n",(long)art)
X		FLUSH;
X	ELSE
X# endif
X# ifdef TERSE
X	    printf("\n(%ld unreadable.)\n",(long)art) FLUSH;
X# endif
X	if (novice_delays) {
X	    pad(just_a_sec);
X	    sleep(2);
X	}
X    }
X    inc_art(selected_only,FALSE);	/* try next article */
X
X#else /* USE_NNTP */
X    ART_NUM artnum;
X
X    clear();
X# ifdef VERBOSE
X    IF(verbose)
X	fputs("Skipping unavailable article\n",stdout);
X    ELSE
X# endif /* VERBOSE */
X# ifdef TERSE
X	fputs("Skipping\n",stdout);
X# endif /* TERSE */
X    if (novice_delays) {
X	pad(just_a_sec/3);
X	sleep(1);
X    }
X    art++;
X    artp++;
X    do {
X	/* tries to grab PREFETCH_SIZE XHDRS, flagging missing articles */
X	(void) fetchsubj(art, FALSE);
X	artnum = art+PREFETCH_SIZE-1;
X	if (artnum > lastart)
X	    artnum = lastart;
X	while (art <= artnum) {
X	    if (!(artp->flags & AF_MISSING))
X		return;
X	    art++;
X	    artp++;
X	}
X    } while (art <= lastart);
X#endif /* USE_NNTP */
X}
X
X/* find the maximum article number of a newsgroup */
X
XART_NUM
Xgetngsize(num)
Xregister NG_NUM num;
X{
X    register int len;
X    register char *nam;
X    char tmpbuf[LBUFLEN];
X    ART_POS oldsoft;
X    long last, first;
X    char ch;
X
X    nam = rcline[num];
X    len = rcnums[num] - 1;
X    softtries++;
X#ifdef DEBUG
X    if (debug & DEB_SOFT_POINTERS)
X	printf("Softptr = %ld\n",(long)softptr[num]) FLUSH;
X#endif
X    oldsoft = softptr[num];
X#ifndef USE_NNTP
X    fseek(actfp,100000L,1);	/* hopefully this forces a reread */
X#endif
X    if ((softptr[num] = findact(tmpbuf, nam, len, (long)oldsoft)) >= 0) {
X	if (softptr[num] != oldsoft) {
X	    softmisses++;
X	    writesoft = TRUE;
X	}
X    }
X    else {
X	softptr[num] = 0;
X	if (RCCHAR(rcchar[num]) == ':')
X	    rcchar[num] = NEGCHAR;
X	return TR_BOGUS;
X    }
X
X#ifdef DEBUG
X    if (debug & DEB_SOFT_POINTERS) {
X	printf("Should be %ld\n",(long)softptr[num]) FLUSH;
X    }
X#endif
X#ifdef ANCIENT_NEWS
X    sscanf(tmpbuf+len+1, "%ld %c", &last, &ch);
X    first = 1;
X#else
X    sscanf(tmpbuf+len+1, "%ld %ld %c", &last, &first, &ch);
X#endif
X    if (!abs1st[num])
X	abs1st[num] = (ART_NUM)first;
X    if (!in_ng) {
X	if (redirected) {
X	    if (redirected != nullstr)
X		free(redirected);
X	    redirected = Nullch;
X	}
X	switch (ch) {
X	case 'n':
X	    moderated = getval("NOPOSTRING"," (no posting)");
X	    break;
X	case 'm':
X	    moderated = getval("MODSTRING", " (moderated)");
X	    break;
X	case 'x':
X	    redirected = nullstr;
X	    moderated = " (DISABLED)";
X	    break;
X	case '=':
X	    len = strlen(tmpbuf);
X	    if (tmpbuf[len-1] == '\n')
X		tmpbuf[len-1] = '\0';
X	    redirected = savestr(rindex(tmpbuf, '=') + 1);
X	    moderated = " (REDIRECTED)";
X	    break;
X	default:
X	    moderated = nullstr;
X	    break;
X	}
X    }
X    if (last < ngmax[num])
X	return ngmax[num];
X    return ngmax[num] = (ART_NUM)last;
X}
SHAR_EOF
  echo 'File trn-3.6/ngdata.c is complete' &&
  $shar_touch -am 1004153894 'trn-3.6/ngdata.c' &&
  chmod 0644 'trn-3.6/ngdata.c' ||
  echo 'restore of trn-3.6/ngdata.c failed'
  shar_count="`wc -c < 'trn-3.6/ngdata.c'`"
  test 6701 -eq "$shar_count" ||
    echo "trn-3.6/ngdata.c: original size 6701, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/ngdata.h ==============
if test -f 'trn-3.6/ngdata.h' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/ngdata.h (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/ngdata.h (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/ngdata.h' &&
X/* $Id: ngdata.h,v 3.0 1992/02/01 03:09:32 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
XEXT FILE *actfp INIT(Nullfp);	/* the active file */
XEXT bool writesoft INIT(FALSE);	/* rewrite the soft pointer file? */
XEXT int softtries INIT(0), softmisses INIT(0);
X
XEXT ART_NUM absfirst INIT(0);	/* 1st real article in current newsgroup */
XEXT ART_NUM firstart INIT(0);	/* minimum unread article number in newsgroup */
XEXT ART_NUM lastart INIT(0);	/* maximum article number in newsgroup */
X
X#ifdef USE_NNTP
XEXT char active_name[MAXFILENAME];
XEXT time_t lastactfetch INIT(0);
X#endif
X
XEXT ART_NUM *abs1st INIT(NULL);	/* 1st real article in newsgroup */
X
XEXT char *moderated;
XEXT char *redirected;
XEXT bool ThreadedGroup;
XEXT long activeitems;			/* number of enties in active file */
X
Xvoid	ngdata_init _((void));
Xbool	access_ng _((void));
Xvoid	grow_ng _((ART_NUM));
Xvoid	ng_skip _((void));
XART_NUM	getngsize _((NG_NUM));
Xvoid	getngmissing _((void));
X
XACT_POS findact _((char*,char*,int,long));
Xvoid ngdatahash_init _((void));
SHAR_EOF
  $shar_touch -am 0812152993 'trn-3.6/ngdata.h' &&
  chmod 0644 'trn-3.6/ngdata.h' ||
  echo 'restore of trn-3.6/ngdata.h failed'
  shar_count="`wc -c < 'trn-3.6/ngdata.h'`"
  test 1577 -eq "$shar_count" ||
    echo "trn-3.6/ngdata.h: original size 1577, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/nghash.c ==============
if test -f 'trn-3.6/nghash.c' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/nghash.c (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/nghash.c (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/nghash.c' &&
X/* $Id: nghash.c,v 3.0 1992/02/01 03:09:32 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber.
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made.
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk.
X */
X
X#include "EXTERN.h"
X#include "common.h"
X#include "ndir.h"
X#include "rcstuff.h"
X#include "trn.h"
X#include "intrp.h"
X#include "final.h"
X#include "rcln.h"
X#include "util.h"
X#include "nntp.h"
X#include "ngdata.h"
X#include "hash.h"
X#include "term.h"
X
Xstatic HASHTABLE *acthash = 0;
Xstatic char *actfile = 0;
X
Xvoid
Xngdatahash_init()
X{
X    register long offset;
X    char *cp = filexp(ACTIVE);
X    struct stat actstat;
X
X#ifdef USE_NNTP
X    nntp_command("LIST");	/* tell server we want the active file */
X    if (nntp_check(TRUE) != NNTP_CLASS_OK) { /* and then see if that's ok */
X	printf("Can't get active file from server: \n%s\n", ser_line);
X	finalize(1);
X    }
X    time(&lastactfetch);
X
X    strcpy(active_name, cp);
X    actfp = fopen(active_name, "w+");	/* and get ready */
X    if (actfp == Nullfp) {
X	printf(cantopen,active_name) FLUSH;
X	finalize(1);
X    }
X
X    while (1) {
X	nntp_gets(ser_line, sizeof ser_line);
X	if (NNTP_LIST_END(ser_line))	/* while there's another line */
X	    break;			/* get it and write it to */
X	fputs(ser_line, actfp);
X	putc('\n', actfp);
X    }
X
X    fflush(actfp);
X    if (ferror(actfp)) {
X	printf("Error writing to active file %s.\n", active_name) FLUSH;
X	finalize(1);
X    }
X    fseek(actfp,0L,0);			/* rewind file */
X
X#else /* !USE_NNTP */
X    actfp = fopen(cp, "r");
X    if (actfp == Nullfp) {
X	printf(cantopen, cp) FLUSH;
X	finalize(1);
X    }
X#endif
X
X    /* rn was being a slug about rereading the active file over and
X     * over; try using a really big buffer to keep it in core. */
X    (void) fstat(fileno(actfp), &actstat);
X    if (actfile)
X	free(actfile);
X    actfile = safemalloc(actstat.st_size + 1);
X
X    /*
X     * NOTE: this won't work on machines with ints too small to hold
X     * the size of the active file.
X     */
X    if (fread(actfile, 1, (int)actstat.st_size, actfp) != actstat.st_size) {
X	fprintf(stderr, "active file not read\n");
X	actfile[0] = '\0';
X    }
X    actfile[actstat.st_size] = '\0';
X
X    if (acthash)
X	hashdestroy(acthash);
X    acthash = hashcreate((int)(actstat.st_size/40), (int (*)())NULL);
X
X    /* count entries */
X    for (activeitems=0, offset=0; offset < actstat.st_size; activeitems++) {
X	register char *p;
X	HASHDATUM data;
X
X	data.dat_ptr = actfile + offset;
X	if ((p = index(data.dat_ptr, '\n')) == NULL)
X	    data.dat_len = actstat.st_size - offset;
X	else
X	    data.dat_len = p - data.dat_ptr + 1;
X	if ((p = index(data.dat_ptr, ' ')) == NULL)
X	    p = data.dat_ptr;
X	hashstore(acthash, data.dat_ptr, p - data.dat_ptr, data);
X	offset += data.dat_len;
X    }
X}
X
XACT_POS
Xfindact(outbuf, nam, len, suggestion)
Xregister char *outbuf;
Xregister char *nam;
Xregister int len;
Xregister long suggestion;
X{
X    register ACT_POS retval;
X
X#ifdef USE_NNTP
X    if (!actfp) {
X	switch (nntp_list("active", nam, len)) {
X	case 0:
X	    return -1;
X	case 1:
X	    strcpy(outbuf, ser_line);
X	    do {
X		nntp_gets(ser_line, sizeof ser_line);
X	    } while (!NNTP_LIST_END(ser_line));
X	    break;
X	}
X	return 0;
X    }
X#endif
X
X    /* see if we know the right place and can just return */
X    if (suggestion != 0 && fseek(actfp, suggestion, 0) >= 0
X     && fgets(outbuf, LBUFLEN, actfp) != NULL && outbuf[len] == ' '
X     && strnEQ(outbuf, nam, len))
X	return suggestion;
X
X    /* hmm, apparently not, gotta look it up. */
X#ifdef DEBUG
X    if (debug & DEB_SOFT_POINTERS)
X	printf("Missed, looking for %s in %sLen = %d\n",nam,outbuf,len) FLUSH;
X#endif
X    /* can we just do a quick hashed lookup? */
X    if (acthash != NULL) {
X	HASHDATUM data;
X
X	outbuf[0] = '\0';
X	data = hashfetch(acthash, nam, len);
X	if (data.dat_ptr == NULL)
X	    return -1;
X	else {
X	    (void) bcopy(data.dat_ptr, outbuf, (int)data.dat_len);
X	    outbuf[data.dat_len] = '\0';
X	    return data.dat_ptr - actfile;
X	}
X    }
X
X    /* apparently not.  gotta do it the slow way. */
X    (void) fseek(actfp, 0L, 0);
X    for (retval=0; fgets(outbuf,LBUFLEN,actfp)!=NULL; retval+=strlen(outbuf))
X	if (outbuf[len] == ' ' && strnEQ(outbuf, nam, len))
X	    break;
X    if (ferror(actfp)) {
X	perror("error on active file");
X	sig_catcher(0);
X	/* NOTREACHED */
X    } else if (feof(actfp))
X	return -1;	/* no such group */
X    return retval;
X}
X
X#ifdef EDIT_DISTANCE
X
X/* Edit Distance extension to trn
X *
X *	Mark Maimone (mwm@cmu.edu)
X *	Carnegie Mellon Computer Science
X *	9 May 1993
X *
X *	This code helps trn handle typos in newsgroup names much more
X *   gracefully.  Instead of "... does not exist!!", it will pick the
X *   nearest one, or offer you a choice if there are several options.
X */
X
X#define LENGTH_HACK 5	/* Don't bother comparing strings with lengths
X			 * that differ by more than this; making this
X			 * smaller than MIN_DIST prunes the number of
X			 * calls to edit_dist().  We won't catch any
X			 * sequences of MIN_DIST INSERTs or DELETEs,
X			 * but we (should!) save some time. */
X#define MAX_NG 9	/* Maximum number of options */
X
X#define ABS(x) (((x) < 0) ? -(x) : (x))
X
Xstatic void check_distance _((HASHDATUM*,int));
X
Xstatic char **ngptrs;		/* List of potential matches */
Xstatic int ngn;			/* Length of list in ngptrs[] */
Xstatic int nglen;		/* Length of name in ngname */
Xstatic int best_match;		/* Value of best match */
X
X/* find_close_match -- Finds the closest match for the string given in
X * global ngname.  If found, the result will be the corrected string
X * returned in that global.
X *
X * We compare the (presumably misspelled) newsgroup name with all legal
X * newsgroups, using the Edit Distance metric.  The edit distance between
X * two strings is the minimum number of simple operations required to
X * convert one string to another (the implementation here supports INSERT,
X * DELETE, CHANGE and SWAP).  This gives every legal newsgroup an integer
X * rank.
X *
X * You might want to present all of the closest matches, and let the user
X * choose among them.  But because I'm lazy I chose to only keep track of
X * all with newsgroups with the *single* smallest error, in array ngptrs[].
X * A more flexible approach would keep around the 10 best matches, whether
X * or not they had precisely the same edit distance, but oh well.
X */
X
Xint
Xfind_close_match()
X{
X   int ret = 0;
X
X    best_match = -1;
X    ngptrs = (char**)safemalloc(MAX_NG * sizeof (char*));
X    ngn = 0;
X    nglen = strlen(ngname);
X
X/* Iterate over all legal newsgroups */
X    hashwalk(acthash, check_distance, 0);
X
X/* ngn is the number of possibilities.  If there's just one, go with it. */
X
X    switch (ngn) {
X        case 0:
X	    break;
X	case 1: {
X	    char *cp = index(ngptrs[0], ' ');
X	    *cp = '\0';
X#ifdef VERBOSE
X	    IF(verbose)
X		printf("(I assume you meant %s)\n", ngptrs[0]) FLUSH;
X	    ELSE
X#endif
X#ifdef TERSE
X		printf("(Using %s)\n", ngptrs[0]) FLUSH;
X#endif
X	    set_ngname(ngptrs[0]);
X	    *cp = ' ';
X	    ret = 1;
X	    break;
X	}
X	default:
X	    ret = get_near_miss();
X	    break;
X    } /* switch */
X    free((char*)ngptrs);
X    return ret;
X} /* find_close_match */
X
Xstatic void
Xcheck_distance(data, ngnum)
XHASHDATUM *data;
Xint ngnum;
X{
X    int value, len;
X    char *cp;
X    if ((cp = index(data->dat_ptr, ' ')) == NULL)
X	return;
X    len = cp - data->dat_ptr;
X
X/* Efficiency:  don't call edit_dist when the lengths are already too
X * different */
X
X    if (ABS(len - nglen) > LENGTH_HACK)
X	return;
X
X    value = edit_distn(ngname, strlen(ngname), data->dat_ptr, len);
X    if (value > MIN_DIST)
X	return;
X
X    if (value < best_match)
X	ngn = 0;
X    if (best_match < 0 || value <= best_match) {
X	best_match = value;
X	if (ngn < MAX_NG)
X	    ngptrs[ngn++] = data->dat_ptr;
X    } /* if */
X}
X
X/* Now we've got several potential matches, and have to choose between them
X * somehow.  Again, results will be returned in global ngname */
X
Xint
Xget_near_miss()
X{
X    char promptbuf[256];
X    char options[MAX_NG+10], *op = options;
X    int i;
X
X#ifdef VERBOSE
X    IF(verbose)
X	printf("However, here are some close matches:\n") FLUSH;
X#endif
X    if (ngn > 9)
X	ngn = 9;	/* Since we're using single digits.... */
X    for (i = 0; i < ngn; i++) {
X	char *cp = index(ngptrs[i], ' ');
X	*cp = '\0';
X	printf("  %d.  %s\n", i+1, ngptrs[i]);
X	sprintf(op++, "%d", i+1);	/* Expensive, but avoids ASCII deps */
X	*cp = ' ';
X    }
X    *op++ = 'n';
X    *op = '\0';
X
X#ifdef VERBOSE
X    IF(verbose)
X	sprintf(promptbuf, "Which of these would you like?");
X    ELSE
X#endif
X#ifdef TERSE
X	sprintf(promptbuf, "Which?");
X#endif
Xreask:
X    in_char(promptbuf, 'A', options);
X#ifdef VERIFY
X    printcmd();
X#endif
X    putchar('\n') FLUSH;
X    switch (*buf) {
X        case 'n':
X	case 'N':
X	case 'q':
X	case 'Q':
X	case 'x':
X	case 'X':
X	    return 0;
X	    break;
X	case 'h':
X	case 'H':
X#ifdef VERBOSE
X	    IF(verbose)
X		fputs("  You entered an illegal newsgroup name, and these are the nearest possible\n  matches.  If you want one of these, then enter its number.  Otherwise\n  just say 'n'.\n", stdout) FLUSH;
X	    ELSE
X#endif
X#ifdef TERSE
X		fputs("Illegal newsgroup, enter a number or 'n'.\n", stdout) FLUSH;
X#endif
X	    goto reask;
X	default:
X	    if (isdigit(*buf)) {
X		char *s = index(options, *buf);
X
X		i = s ? (s - options) : ngn;
X		if (i >= 0 && i < ngn) {
X		    char *cp = index(ngptrs[i], ' ');
X		    *cp = '\0';
X		    set_ngname(ngptrs[i]);
X		    *cp = ' ';
X		    return 1;
X		} /* if */
X	    } /* if */
X	    fputs(hforhelp, stdout) FLUSH;
X	    break;
X    } /* switch */
X
X    settle_down();
X    goto reask;
X} /* get_near_miss */
X
X#endif /* EDIT_DISTANCE */
SHAR_EOF
  $shar_touch -am 1004153894 'trn-3.6/nghash.c' &&
  chmod 0644 'trn-3.6/nghash.c' ||
  echo 'restore of trn-3.6/nghash.c failed'
  shar_count="`wc -c < 'trn-3.6/nghash.c'`"
  test 9954 -eq "$shar_count" ||
    echo "trn-3.6/nghash.c: original size 9954, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/ngsrch.c ==============
if test -f 'trn-3.6/ngsrch.c' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/ngsrch.c (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/ngsrch.c (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/ngsrch.c' &&
X/* $Id: ngsrch.c,v 3.0 1992/02/01 03:09:32 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#include "EXTERN.h"
X#include "common.h"
X#include "rcstuff.h"
X#include "final.h"
X#include "search.h"
X#include "trn.h"
X#include "util.h"
X#include "term.h"
X#include "rcln.h"
X#include "INTERN.h"
X#include "ngsrch.h"
X
X#ifdef NGSEARCH
XCOMPEX ngcompex;
X#endif
X
Xvoid
Xngsrch_init()
X{
X#ifdef NGSEARCH
X    init_compex(&ngcompex)
X#endif
X    ;
X}
X
X#ifdef NGSEARCH
Xint
Xng_search(patbuf,get_cmd)
Xchar *patbuf;				/* if patbuf != buf, get_cmd must */
Xint get_cmd;				/*   be set to FALSE!!! */
X{
X    char *pattern;			/* unparsed pattern */
X    register char cmdchr = *patbuf;	/* what kind of search? */
X    register char *s;
X    bool backward = cmdchr == '?';	/* direction of search */
X
X    int_count = 0;
X    if (get_cmd && buf == patbuf)
X	if (!finish_command(FALSE))		/* get rest of command */
X	    return NGS_ABORT;
X    for (pattern = patbuf+1; *pattern == ' '; pattern++) ;
X    if (*pattern) {
X	ng_doread = FALSE;
X    }
X    s = rindex(pattern,cmdchr);
X    if (s != Nullch && *(s-1) != '\\') {
X	*s++ = '\0';
X	if (index(s,'r') != Nullch)
X	    ng_doread = TRUE;
X    }
X    if ((s = ng_comp(&ngcompex,pattern,TRUE,TRUE)) != Nullch) {
X					/* compile regular expression */
X	printf("\n%s\n",s) FLUSH;
X	return NGS_ERROR;
X    }
X    fputs("\nSearching...",stdout) FLUSH;	/* give them something to read */
X    fflush(stdout);
X    for (;;) {
X	if (int_count) {
X	    int_count = 0;
X	    return NGS_INTR;
X	}
X	if (backward) {
X	    if (ng > 0)
X		--ng;
X	    else
X		ng = nextrcline;
X	}
X	else {
X	    if (ng >= nextrcline)
X		ng = 0;
X	    else
X		++ng;
X	}
X	if (ng == current_ng)
X	    return NGS_NOTFOUND;
X	if (ng == nextrcline || toread[ng] < TR_NONE || !ng_wanted())
X	    continue;
X	if (toread[ng] == TR_NONE)
X	    set_toread(ng);
X	
X	if (toread[ng] > TR_NONE)
X	    return NGS_FOUND;
X	else if (toread[ng] == TR_NONE)
X	    if (ng_doread)
X		return NGS_FOUND;
X	    else
X		printf("\n[0 unread in %s -- skipping]",rcline[ng]) FLUSH;
X    }
X}
X
Xbool
Xng_wanted()
X{
X    return execute(&ngcompex,rcline[ng]) != Nullch;
X}
X#endif /* NGSEARCH */
X
X#ifdef NGSORONLY
Xchar *
Xng_comp(compex,pattern,RE,fold)
XCOMPEX *compex;
Xchar *pattern;
Xbool_int RE;
Xbool_int fold;
X{
X    char ng_pattern[128];
X    register char *s = pattern, *d = ng_pattern;
X
X    if (!*s) {
X	if (compile(compex, "", RE, fold))
X	    return "No previous search pattern";
X	else
X	    return Nullch;			/* reuse old pattern */
X    }
X    for (; *s; s++) {
X	if (*s == '.') {
X	    *d++ = '\\';
X	    *d++ = *s;
X	}
X	else if (*s == '?') {
X	    *d++ = '.';
X	}
X	else if (*s == '*') {
X	    *d++ = '.';
X	    *d++ = *s;
X	}
X#if OLD_RN_WAY
X	else if (strnEQ(s,"all",3)) {
X	    *d++ = '.';
X	    *d++ = '*';
X	    s += 2;
X	}
X#endif
X	else
X	    *d++ = *s;
X    }
X    *d = '\0';
X    return compile(compex,ng_pattern,RE,fold);
X}
X#endif
X
SHAR_EOF
  $shar_touch -am 0823175594 'trn-3.6/ngsrch.c' &&
  chmod 0644 'trn-3.6/ngsrch.c' ||
  echo 'restore of trn-3.6/ngsrch.c failed'
  shar_count="`wc -c < 'trn-3.6/ngsrch.c'`"
  test 3373 -eq "$shar_count" ||
    echo "trn-3.6/ngsrch.c: original size 3373, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/ngsrch.h ==============
if test -f 'trn-3.6/ngsrch.h' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/ngsrch.h (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/ngsrch.h (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/ngsrch.h' &&
X/* $Id: ngsrch.h,v 3.0 1992/02/01 03:09:32 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#ifdef NGSEARCH
X#define NGS_ABORT 0
X#define NGS_FOUND 1
X#define NGS_INTR 2
X#define NGS_NOTFOUND 3
X#define NGS_ERROR 4
X
XEXT bool ng_doread INIT(FALSE);		/* search read newsgroups? */
X#endif
X
Xvoid	ngsrch_init _((void));
X#ifdef NGSEARCH 
Xint	ng_search _((char*,int));
Xbool	ng_wanted _((void));
X#endif
X#ifdef NGSORONLY
Xchar	*ng_comp _((COMPEX*,char*,bool_int,bool_int));
X#endif
SHAR_EOF
  $shar_touch -am 0711223693 'trn-3.6/ngsrch.h' &&
  chmod 0644 'trn-3.6/ngsrch.h' ||
  echo 'restore of trn-3.6/ngsrch.h failed'
  shar_count="`wc -c < 'trn-3.6/ngsrch.h'`"
  test 1025 -eq "$shar_count" ||
    echo "trn-3.6/ngsrch.h: original size 1025, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/ngstuff.c ==============
if test -f 'trn-3.6/ngstuff.c' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/ngstuff.c (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/ngstuff.c (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/ngstuff.c' &&
X/* $Id: ngstuff.c,v 3.0 1991/09/09 20:23:31 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#include "EXTERN.h"
X#include "common.h"
X#include "term.h"
X#include "util.h"
X#include "util2.h"
X#include "cache.h"
X#include "bits.h"
X#include "ngdata.h"
X#include "ng.h"
X#include "intrp.h"
X#include "head.h"
X#include "final.h"
X#include "sw.h"
X#include "rthread.h"
X#include "rt-select.h"
X#include "rt-wumpus.h"
X#include "trn.h"
X#include "rcstuff.h"
X#include "respond.h"
X#include "kfile.h"
X#include "decode.h"
X#include "INTERN.h"
X#include "ngstuff.h"
X
Xvoid
Xngstuff_init()
X{
X    ;
X}
X
X/* do a shell escape */
X
Xint
Xescapade()
X{
X    register char *s;
X    bool interactive = (buf[1] == FINISHCMD);
X    bool docd;
X    char whereiam[512];
X
X    if (!finish_command(interactive))	/* get remainder of command */
X	return -1;
X    s = buf+1;
X    docd = *s != '!';
X    if (!docd) {
X	s++;
X    }
X    else {
X	getwd(whereiam);
X	if (chdir(cwd)) {
X	    printf(nocd,cwd) FLUSH;
X	    sig_catcher(0);
X	}
X    }
X    while (*s == ' ') s++;
X					/* skip leading spaces */
X    interp(cmd_buf, (sizeof cmd_buf), s);/* interpret any % escapes */
X    resetty();				/* make sure tty is friendly */
X    doshell(Nullch,cmd_buf);	/* invoke the shell */
X    noecho();				/* and make terminal */
X    crmode();				/*   unfriendly again */
X    if (docd) {
X	if (chdir(whereiam)) {
X	    printf(nocd,whereiam) FLUSH;
X	    sig_catcher(0);
X	}
X    }
X#ifdef MAILCALL
X    mailcount = 0;			/* force recheck */
X#endif
X    return 0;
X}
X
X/* process & command */
X
Xint
Xswitcheroo()
X{
X    if (!finish_command(TRUE)) /* get rest of command */
X	return -1;	/* if rubbed out, try something else */
X    if (!buf[1])
X	pr_switches();
X    else if (buf[1] == '&') {
X	if (!buf[2]) {
X	    page_init();
X	    show_macros();
X	}
X	else {
X	    char tmpbuf[LBUFLEN];
X	    register char *s;
X
X	    for (s=buf+2; isspace(*s); s++);
X	    mac_line(s,tmpbuf,(sizeof tmpbuf));
X	}
X    }
X    else {
X	bool docd = (instr(buf,"-d", TRUE) != Nullch);
X 	char whereami[512];
X 
X	if (docd)
X	    getwd(whereami);
X	sw_list(buf+1);
X	if (docd) {
X	    cwd_check();
X	    if (chdir(whereami)) {		/* -d does chdirs */
X		printf(nocd,whereami) FLUSH;
X		sig_catcher(0);
X	    }
X	}
X    }
X    return 0;
X}
X
X/* process range commands */
X
Xint
Xnumnum()
X{
X    ART_NUM min, max;
X    char *cmdlst = Nullch;
X    register char *s, *c;
X    ART_NUM oldart = art;
X    char tmpbuf[LBUFLEN];
X    bool justone = TRUE;		/* assume only one article */
X
X    perform_cnt = 0;
X    if (!finish_command(TRUE))	/* get rest of command */
X	return NN_INP;
X	if (lastart < 1) {
X	    fputs("\nNo articles\n",stdout) FLUSH;
X	    return NN_ASK;
X	}
X#ifdef ARTSEARCH
X    if (srchahead)
X	srchahead = -1;
X#endif
X    for (s=buf; *s && (isdigit(*s) || index(" ,-.$",*s)); s++)
X	if (!isdigit(*s))
X	    justone = FALSE;
X    if (*s) {
X	cmdlst = savestr(s);
X	justone = FALSE;
X    }
X    else if (!justone)
X	cmdlst = savestr("m");
X    *s++ = ',';
X    *s = '\0';
X    safecpy(tmpbuf,buf,LBUFLEN);
X    for (s = tmpbuf; c = index(s,','); s = ++c) {
X	*c = '\0';
X	if (*s == '.')
X	    min = oldart;
X	else
X	    min = atol(s);
X	if (min < absfirst) {
X	    min = absfirst;
X	    printf("(First article is %ld)\n",(long)absfirst) FLUSH;
X	    pad(just_a_sec/3);
X	}
X	if ((s=index(s,'-')) != Nullch) {
X	    s++;
X	    if (*s == '$')
X		max = lastart;
X	    else if (*s == '.')
X		max = oldart;
X	    else
X		max = atol(s);
X	}
X	else
X	    max = min;
X	if (max>lastart) {
X	    max = lastart;
X	    if (min > max)
X		min = max;
X	    printf("(Last article is %ld)\n",(long)lastart) FLUSH;
X	    pad(just_a_sec/3);
X	}
X	if (max < min) {
X	    fputs("\nBad range\n",stdout) FLUSH;
X	    if (cmdlst)
X		free(cmdlst);
X	    return NN_ASK;
X	}
X	if (justone) {
X	    art = min;
X	    return NN_REREAD;
X	}
X	for (art=min, artp=article_ptr(min); art<=max; art++, artp++) {
X	    if (perform(cmdlst,TRUE)) {
X#ifdef VERBOSE
X		IF(verbose)
X		    printf("\n(Interrupted at article %ld)\n",(long)art)
X		      FLUSH;
X		ELSE
X#endif
X#ifdef TERSE
X		    printf("\n(Intr at %ld)\n",(long)art) FLUSH;
X#endif
X		if (cmdlst)
X		    free(cmdlst);
X		return NN_ASK;
X	    }
X	}
X    }
X    art = oldart;
X    if (cmdlst)
X	free(cmdlst);
X    return NN_NORM;
X}
X
Xint
Xthread_perform()
X{
X    register SUBJECT *sp;
X    register ARTICLE *ap;
X    bool want_read;
X    char *cmdstr;
X    int len, ret = 1;
X    int bits;
X    bool one_thread = FALSE;
X
X    if (!finish_command(TRUE))	/* get rest of command */
X	return 0;
X    if (!buf[1])
X	return -1;
X    len = 1;
X    if (buf[1] == ':') {
X	bits = 0;
X	len++;
X    }
X    else
X	bits = SF_VISIT;
X    if (buf[len] == '.') {
X	if (!artp)
X	    return -1;
X	one_thread = TRUE;
X	len++;
X    }
X    cmdstr = savestr(buf+len);
X    want_read = (sel_rereading || *cmdstr == 'm');
X
X    perform_cnt = 0;
X    page_line = 1;
X    len = strlen(cmdstr);
X
X    /* A few commands can just loop through the subjects. */
X    if ((len == 1 && (*cmdstr == 't' || *cmdstr == 'J'))
X     || (len == 2
X      && (((*cmdstr == '+' || *cmdstr == '-') && cmdstr[0] == cmdstr[1])
X       || *cmdstr == 'T'))) {
X	if (one_thread)
X	    sp = (sel_mode==SM_THREAD? artp->subj->thread->subj : artp->subj);
X	else
X	    sp = next_subj(Nullsubj,bits);
X	for ( ; sp; sp = next_subj(sp,bits)) {
X	    if (!(sp->flags & sel_mask) ^ !bits)
X		continue;
X	    artp = first_art(sp);
X	    if (artp) {
X		art = article_num(artp);
X		if (perform(cmdstr, FALSE)) {
X		    fputs("\nInterrupted\n", stdout) FLUSH;
X		    goto break_out;
X		}
X	    }
X#ifdef VERBOSE
X	    IF(verbose)
X		if (mode != 't' && *cmdstr != 't' && *cmdstr != 'T')
X		    putchar('\n') FLUSH;
X#endif
X	    if (one_thread)
X		break;
X	}
X    } else if (strEQ(cmdstr, "E")) {
X	/* The 'E'nd-decode command doesn't do any looping at all. */
X	if (decode_fp)
X	    decode_end();
X	else
X	    ret = 2;
X    } else if (*cmdstr == 'p') {
X	ART_NUM oldart = art;
X	art = lastart+1;
X	followup();
X	forcegrow = TRUE;
X	art = oldart;
X    } else {
X	/* The rest loop through the articles. */
X	/* Use the explicit article-order if it exists */
X	if (artptr_list) {
X	    ARTICLE **app, **limit = artptr_list + artptr_list_size;
X	    sp = (sel_mode==SM_THREAD? artp->subj->thread->subj : artp->subj);
X	    for (app = artptr_list; app < limit; app++) {
X		ap = *app;
X		if (one_thread && ap->subj->thread != sp->thread)
X		    continue;
X		if ((!(ap->flags & AF_READ) ^ want_read)
X		 && !(ap->flags & sel_mask) ^ !!bits) {
X		    art = article_num(ap);
X		    artp = ap;
X		    if (perform(cmdstr, TRUE)) {
X			fputs("\nInterrupted\n", stdout) FLUSH;
X			goto break_out;
X		    }
X		}
X	    }
X	} else {
X	    if (one_thread)
X		sp = (sel_mode==SM_THREAD? artp->subj->thread->subj : artp->subj);
X	    else
X		sp = next_subj(Nullsubj,bits);
X	    for ( ; sp; sp = next_subj(sp,bits)) {
X		for (ap = first_art(sp); ap; ap = next_art(ap))
X		    if ((!(ap->flags & AF_READ) ^ want_read)
X		     && !(ap->flags & sel_mask) ^ !!bits) {
X			art = article_num(ap);
X			artp = ap;
X			if (perform(cmdstr, TRUE)) {
X			    fputs("\nInterrupted\n", stdout) FLUSH;
X			    goto break_out;
X			}
X		    }
X		if (one_thread)
X		    break;
X	    }
X	}
X    }
X  break_out:
X    free(cmdstr);
X    return ret;
X}
X
Xint
Xperform(cmdlst,toplevel)
Xregister char *cmdlst;
Xint toplevel;
X{
X    register int ch;
X    bool saveit = FALSE;
X    
X    if (toplevel) {
X	printf("%-6ld ",art);
X	fflush(stdout);
X    }
X    perform_cnt++;
X    for (; ch = *cmdlst; cmdlst++) {
X	if (isspace(ch) || ch == ':')
X	    continue;
X	if (ch == 'j' && !saveit) {
X	    if (!was_read(art)) {
X		mark_as_read();
X#ifdef VERBOSE
X		IF(verbose)
X		    fputs("\tJunked",stdout);
X#endif
X	    }
X	    if (sel_rereading)
X		deselect_article(artp);
X	} else if (ch == '+') {
X	    if (saveit || cmdlst[1] == '+') {
X		if (sel_mode == SM_THREAD)
X		    select_arts_thread(artp, saveit? AUTO_SELECTALL : 0);
X		else
X		    select_arts_subject(artp, saveit? AUTO_SELECTALL : 0);
X		if (cmdlst[1] == '+')
X		    cmdlst++;
X	    } else
X		select_article(artp, AUTO_ECHO);
X	} else if (ch == '.') {
X	    select_subthread(artp, saveit? AUTO_SELECT : 0);
X	} else if (ch == '-') {
X	    if (cmdlst[1] == '-') {
X		if (sel_mode == SM_THREAD)
X		    deselect_arts_thread(artp);
X		else
X		    deselect_arts_subject(artp);
X		cmdlst++;
X	    } else
X		deselect_article(artp);
X	} else if (ch == ',') {
X	    kill_subthread(artp, saveit? (KF_ALL|KF_KILLFILE) : KF_ALL);
X	} else if (ch == 'J' || ch == 'j') {
X	    if (sel_mode == SM_THREAD)
X		kill_arts_thread(artp, saveit? (KF_ALL|KF_KILLFILE) : KF_ALL);
X	    else
X		kill_arts_subject(artp, saveit? (KF_ALL|KF_KILLFILE) : KF_ALL);
X	} else if (ch == 'x') {
X	    if (!was_read(art)) {
X		oneless(artp);
X#ifdef VERBOSE
X		IF(verbose)
X		    fputs("\tKilled",stdout);
X#endif
X	    }
X	    if (sel_rereading)
X		deselect_article(artp);
X	} else if (ch == 't') {
X	    entire_tree(artp);
X	} else if (ch == 'T') {
X	    saveit = TRUE;
X	} else if (ch == 'm') {
X	    if ((article_ptr(art)->flags & (AF_READ|AF_MISSING)) == AF_READ) {
X		unmark_as_read();
X#ifdef VERBOSE
X		IF(verbose)
X		    fputs("\tMarked unread",stdout);
X#endif
X	    }
X	}
X	else if (ch == 'M') {
X	    delay_unmark(artp);
X	    oneless(artp);
X#ifdef VERBOSE
X	    IF(verbose)
X		fputs("\tWill return",stdout);
X#endif
X	}
X	else if (ch == '=') {
X	    printf("\t%s",fetchsubj(art,FALSE));
X#ifdef VERBOSE
X	    IF(verbose)
X		;
X	    ELSE
X#endif
X		putchar('\n') FLUSH;		/* ghad! */
X	}
X	else if (ch == 'C') {
X#ifdef ASYNC_PARSE
X	    printf("\t%sancelled",(cancel_article() ? "Not c" : "C"));
X#else
X	    notincl("C");
X	    return -1;
X#endif
X	}
X	else if (ch == '%') {
X#ifdef ASYNC_PARSE
X	    char tmpbuf[512];
X
X	    if (one_command)
X		interp(tmpbuf, (sizeof tmpbuf), cmdlst);
X	    else
X		cmdlst = dointerp(tmpbuf, (sizeof tmpbuf), cmdlst, ":") - 1;
X	    perform_cnt--;
X	    if (perform(tmpbuf,FALSE))
X		return -1;
X#else
X	    notincl("%");
X	    return -1;
X#endif
X	}
X	else if (index("!&sSwWe|",ch)) {
X	    if (one_command)
X		strcpy(buf,cmdlst);
X	    else
X		cmdlst = cpytill(buf,cmdlst,':') - 1;
X	    /* we now have the command in buf */
X	    if (ch == '!') {
X		escapade();
X#ifdef VERBOSE
X		IF(verbose)
X		    fputs("\tShell escaped",stdout);
X#endif
X	    }
X	    else if (ch == '&') {
X		switcheroo();
X#ifdef VERBOSE
X		IF(verbose)
X		    if (buf[1] && buf[1] != '&')
X			fputs("\tSwitched",stdout);
X#endif
X	    }
X	    else {
X		putchar('\t');
X		save_article();
X#ifdef VERBOSE
X		IF(verbose)
X		    ;
X		ELSE
X#endif
X		    putchar('\n') FLUSH;
X	    }
X	}
X	else {
X	    printf("\t???%s\n",cmdlst);
X	    return -1;
X	}
X#ifdef VERBOSE
X	fflush(stdout);
X#endif
X	if (one_command)
X	    break;
X    }
X    if (toplevel) {
X#ifdef VERBOSE
X	IF(verbose)
X	    putchar('\n') FLUSH;
X#endif
X    }
X    if (int_count) {
X	int_count = 0;
X	return -1;
X    }
X    return 0;
X}
SHAR_EOF
  $shar_touch -am 1118220194 'trn-3.6/ngstuff.c' &&
  chmod 0644 'trn-3.6/ngstuff.c' ||
  echo 'restore of trn-3.6/ngstuff.c failed'
  shar_count="`wc -c < 'trn-3.6/ngstuff.c'`"
  test 11106 -eq "$shar_count" ||
    echo "trn-3.6/ngstuff.c: original size 11106, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/ngstuff.h ==============
if test -f 'trn-3.6/ngstuff.h' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/ngstuff.h (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/ngstuff.h (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/ngstuff.h' &&
X/* $Id: ngstuff.h,v 3.0 1991/09/09 20:23:31 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#define NN_NORM 0
X#define NN_INP 1
X#define NN_REREAD 2
X#define NN_ASK 3
X
XEXT bool one_command INIT(FALSE);	/* no ':' processing in perform() */
X
Xvoid	ngstuff_init _((void));
Xint	escapade _((void));
Xint	switcheroo _((void));
Xint	numnum _((void));
Xint	perform _((char*,int));
Xint	thread_perform _((void));
SHAR_EOF
  $shar_touch -am 1118220194 'trn-3.6/ngstuff.h' &&
  chmod 0644 'trn-3.6/ngstuff.h' ||
  echo 'restore of trn-3.6/ngstuff.h failed'
  shar_count="`wc -c < 'trn-3.6/ngstuff.h'`"
  test 956 -eq "$shar_count" ||
    echo "trn-3.6/ngstuff.h: original size 956, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/nntp.c ==============
if test -f 'trn-3.6/nntp.c' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/nntp.c (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/nntp.c (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/nntp.c' &&
X/* $Id: nntp.c,v 3.0 1991/11/22 04:12:21 davison Trn $
X*/
X/* The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#include "EXTERN.h"
X#include "common.h"
X#include "util.h"
X#include "init.h"
X#include "trn.h"
X#include "ngdata.h"
X#include "rcln.h"
X#include "cache.h"
X#include "bits.h"
X#include "head.h"
X#include "term.h"
X#include "final.h"
X#include "artio.h"
X#include "nntp.h"
X
X#ifdef USE_NNTP
X
Xint
Xnntp_list(type, arg, len)
Xchar *type;
Xchar *arg;
Xint len;
X{
X    int pos;
X    sprintf(ser_line, "list %s ", type);
X    pos = strlen(ser_line);
X    strncpy(ser_line+pos, arg, len);
X    ser_line[pos+len] = '\0';
X    nntp_command(ser_line);
X    if (nntp_check(FALSE) != NNTP_CLASS_OK) {
X	if (*ser_line == NNTP_CLASS_FATAL)
X	    return -1;
X	return 0;
X    }
X    nntp_gets(ser_line, sizeof ser_line);
X#if defined(DEBUG) && defined(FLUSH)
X    if (debug & DEB_NNTP)
X	printf("<%s\n", ser_line) FLUSH;
X#endif
X    if (NNTP_LIST_END(ser_line))
X	return 0;
X    return 1;
X}
X
X/* try to access the specified group */
X
Xbool
Xnntp_group(group, num)
Xchar *group;
XNG_NUM num;
X{
X    sprintf(ser_line, "GROUP %s", group);
X    nntp_command(ser_line);
X    if (nntp_check(FALSE) != NNTP_CLASS_OK) {
X	int ser_int = atoi(ser_line);
X	if (ser_int != NNTP_NOSUCHGROUP_VAL
X	 && ser_int != NNTP_SYNTAX_VAL) {
X	    if (ser_int != NNTP_AUTH_NEEDED_VAL && ser_int != NNTP_ACCESS_VAL
X	     && ser_int != NNTP_AUTH_REJECT_VAL) {
X		fprintf(stderr, "\nServer's response to GROUP %s:\n%s\n",
X			group, ser_line);
X		finalize(1);
X	    }
X	}
X	return FALSE;
X    }
X    if (num >= 0) {
X	long count, first, last;
X
X	(void) sscanf(ser_line,"%*d%ld%ld%ld",&count,&first,&last);
X	/* NNTP mangles the high/low values when no articles are present. */
X	if (!count)
X	    abs1st[num] = ngmax[num]+1;
X	else {
X	    abs1st[num] = (ART_NUM)first;
X	    if (last > ngmax[num])
X		ngmax[num] = (ART_NUM)last;
X	}
X    }
X    return TRUE;
X}
X
X/* check on an article's existence */
X
Xbool
Xnntp_stat(artnum)
XART_NUM artnum;
X{
X    sprintf(ser_line, "STAT %ld", (long)artnum);
X    nntp_command(ser_line);
X    return (nntp_check(TRUE) == NNTP_CLASS_OK);
X}
X
X/* check on an article's existence by its message id */
X
XART_NUM
Xnntp_stat_id(msgid)
Xchar *msgid;
X{
X    long artnum;
X
X    sprintf(ser_line, "STAT %s", msgid);
X    nntp_command(ser_line);
X    if (nntp_check(TRUE) != NNTP_CLASS_OK
X     || sscanf(ser_line, "%*d%ld", &artnum) != 1)
X	return 0;
X    return (ART_NUM)artnum;
X}
X
X/* prepare to get the header */
X
Xbool
Xnntp_header(artnum)
XART_NUM artnum;
X{
X    sprintf(ser_line, "HEAD %ld", (long)artnum);
X    nntp_command(ser_line);
X    return (nntp_check(TRUE) == NNTP_CLASS_OK);
X}
X
X/* copy the body of an article to a temporary file */
X
Xstatic bool nntp_copybody _((char*,int,long));
Xstatic long body_pos = -1;
Xstatic long body_end = 0;
X
XFILE *
Xnntp_body(artnum)
XART_NUM artnum;
X{
X    char *artname;
X    FILE *fp;
X
X    if (!parseheader(artnum))
X	return Nullfp;
X    artname = nntp_artname();
X    if (!(fp = fopen(artname, "w+"))) {
X	fprintf(stderr, "\nUnable to write temporary file: '%s'.\n",
X		artname);
X	finalize(1);
X    }
X    sprintf(ser_line, "BODY %ld", (long)artnum);
X    nntp_command(ser_line);
X    if (nntp_check(TRUE) != NNTP_CLASS_OK) {	/* and get it's reaction */
X	fclose(fp);
X	errno = ENOENT;			/* Simulate file-not-found */
X	return Nullfp;
X    }
X    fwrite(headbuf, 1, strlen(headbuf), fp);
X    body_pos = 0;
X    body_end = ftell(fp);
X    htype[PAST_HEADER].ht_minpos = body_end;
X#ifdef GRAB_IT_ALL
X    nntp_copybody(ser_line, sizeof ser_line, 0x7fffffffL);
X#endif
X    fseek(fp, 0L, 0);
X    return fp;
X}
X
Xlong
Xnntp_artsize()
X{
X    return body_pos < 0 ? body_end : -1;
X}
X
Xstatic bool
Xnntp_copybody(s, limit, pos)
Xchar *s;
Xint limit;
Xlong pos;
X{
X    int len;
X    bool had_nl = TRUE;
X    bool found_nl;
X    while (pos > body_end || !had_nl) {
X	found_nl = nntp_gets(s, limit-1);
X	if (had_nl) {
X	    if (NNTP_LIST_END(s)) {
X		fseek(artfp, body_pos, 0);
X		body_pos = -1;
X		return FALSE;
X	    }
X	    if (s[0] == '.') {
X		strcpy(s, s+1);
X	    }
X	}
X	len = strlen(s);
X	if (found_nl)
X	    strcpy(s+len, "\n");
X	fputs(s, artfp);
X	body_end = ftell(artfp);
X	had_nl = found_nl;
X    }
X    return TRUE;
X}
X
Xint
Xnntp_finishbody(bmode)
Xint bmode;
X{
X    char b[NNTP_STRLEN];
X    if (body_pos < 0)
X	return FALSE;
X    if (bmode == FB_OUTPUT) {
X#ifdef VERBOSE
X	IF(verbose)
X	    printf("Receiving the rest of the article..."), fflush(stdout);
X	ELSE
X#endif
X#ifdef TERSE
X	    printf("Receiving..."), fflush(stdout);
X#endif
X    }
X    if (body_end != body_pos)
X	fseek(artfp, body_end, 0);
X    if (bmode != FB_BACKGROUND)
X	nntp_copybody(b, sizeof b, 0x7fffffffL);
X    else {
X	while (nntp_copybody(b, sizeof b, body_end+1)) {
X	    if (input_pending())
X		break;
X	}
X	if (body_pos >= 0)
X	    fseek(artfp, body_pos, 0);
X    }
X    if (bmode == FB_OUTPUT) {
X	carriage_return();
X	erase_eol();	/* erase the prompt */
X	carriage_return();	/* Resets kernel's tab column counter to 0 */
X    }
X    return TRUE;
X}
X
Xvoid
Xnntp_seekart(pos)
Xlong pos;
X{
X    if (body_pos >= 0) {
X	if (body_end < pos) {
X	    char b[NNTP_STRLEN];
X	    fseek(artfp, body_end, 0);
X	    nntp_copybody(b, sizeof b, pos);
X	    if (body_pos >= 0)
X		body_pos = pos;
X	}
X	else
X	    body_pos = pos;
X    }
X    fseek(artfp, pos, 0);
X}
X
Xlong
Xnntp_tellart()
X{
X    return body_pos < 0 ? ftell(artfp) : body_pos;
X}
X
Xchar *
Xnntp_readart(s, limit)
Xchar *s;
Xint limit;
X{
X    if (body_pos >= 0) {
X	if (body_pos == body_end) {
X	    if (!nntp_copybody(s, limit, body_pos+1))
X		return Nullch;
X	    body_pos = body_end;
X	    return s;
X	}
X	s = fgets(s, limit, artfp);
X	body_pos = ftell(artfp);
X	if (body_pos == body_end)
X	    fseek(artfp, body_pos, 0);
X	return s;
X    }
X    return fgets(s, limit, artfp);
X}
X
X/* This is a 1-relative list */
Xstatic int maxdays[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
X
Xtime_t
Xnntp_time()
X{
X    char *s;
X    int year, month, day, hh, mm;
X    time_t ss;
X
X    nntp_command("XDATE" + (CompliantServer? 0 : 1));
X    if (nntp_check(FALSE) != NNTP_CLASS_INF)
X	return time((time_t*)NULL);
X
X    s = rindex(ser_line, ' ') + 1;
X    month = (s[4] - '0') * 10 + (s[5] - '0');
X    day = (s[6] - '0') * 10 + (s[7] - '0');
X    hh = (s[8] - '0') * 10 + (s[9] - '0');
X    mm = (s[10] - '0') * 10 + (s[11] - '0');
X    ss = (s[12] - '0') * 10 + (s[13] - '0');
X    s[4] = '\0';
X    year = atoi(s);
X
X    /* This simple algorithm will be valid until the year 2400 */
X    if (year % 4)
X	maxdays[2] = 28;
X    else
X	maxdays[2] = 29;
X    if (month < 1 || month > 12 || day < 1 || day > maxdays[month]
X     || hh < 0 || hh > 23 || mm < 0 || mm > 59
X     || ss < 0 || ss > 59)
X	return time((time_t*)NULL);
X
X    for (month--; month; month--)
X	day += maxdays[month];
X
X    ss = ((((year-1970) * 365 + (year-1968)/4 + day - 1) * 24L + hh) * 60
X	  + mm) * 60 + ss;
X
X    return ss;
X}
X
Xbool
Xnntp_newgroups(t)
Xtime_t t;
X{
X    struct tm *ts;
X
X    ts = gmtime(&t);
X    sprintf(ser_line, "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT",
X	ts->tm_year % 100, ts->tm_mon+1, ts->tm_mday,
X	ts->tm_hour, ts->tm_min, ts->tm_sec);
X    nntp_command(ser_line);
X    return (nntp_check(TRUE) == NNTP_CLASS_OK);
X}
X
Xbool
Xnntp_listgroup()
X{
X#ifdef NO_LISTGROUP
X    static bool listgroup_works = FALSE;
X#else
X    static bool listgroup_works = TRUE;
X#endif
X
X    if (!listgroup_works)
X	return FALSE;
X    nntp_command("XLISTGROUP" + (CompliantServer? 0 : 1));
X    if (nntp_check(FALSE) != NNTP_CLASS_OK) {
X	listgroup_works = FALSE;
X	return FALSE;
X    }
X    return TRUE;
X}
X
Xchar *
Xnntp_artname()
X{
X    static char artname[20];
X    sprintf(artname,"rrn.%ld",our_pid);
X    return artname;
X}
X
Xchar
Xnntp_handle_timeout(strict)
Xbool_int strict;
X{
X    static bool handling_timeout = FALSE;
X    char last_command_save[NNTP_STRLEN];
X    char ch;
X
X    if (handling_timeout)
X	return NNTP_CLASS_FATAL;
X    handling_timeout = TRUE;
X    strcpy(last_command_save, last_command);
X    nntp_close(FALSE);
X    if (!nntp_connect(0) || (in_ng && !nntp_group(ngname, -1))) {
X	nntp_error("\n503 Server timed out.\n");
X	finalize(1);
X    }
X    nntp_command(last_command_save);
X    ch = nntp_check(strict);
X    handling_timeout = FALSE;
X    return ch;
X}
X
X/* cleanup the odds and ends associated with NNTP usage */
X
Xvoid
Xnntp_cleanup()
X{
X    UNLINK(nntp_artname());
X    if (*active_name)
X	UNLINK(active_name);
X    nntp_close(TRUE);
X}
X
X#ifdef USE_XTHREAD
X
Xstatic long rawbytes = -1;	/* bytes remaining to be transfered */
X
X/* nntp_readcheck -- get a line of text from the server, interpreting
X** it as a status message for a binary command.  Call this once
X** before calling nntp_read() for the actual data transfer.
X*/
Xlong
Xnntp_readcheck()
X{
X    /* try to get the status line and the status code */
X    if (nntp_check(FALSE) != NNTP_CLASS_OK)
X	return rawbytes = -1;
X
X    /* try to get the number of bytes being transfered */
X    if (sscanf(ser_line, "%*d%ld", &rawbytes) != 1)
X	return rawbytes = -1;
X    return rawbytes;
X}
X
X/* nntp_read -- read data from the server in binary format.  This call must
X** be preceeded by an appropriate binary command and an nntp_readcheck call.
X*/
Xlong
Xnntp_read(buf, n)
Xchar *buf;
Xlong n;
X{
X    /* if no bytes to read, then just return EOF */
X    if (rawbytes < 0)
X	return 0;
X
X#ifdef HAS_SIGHOLD
X    sighold(SIGINT);
X#endif
X
X    /* try to read some data from the server */
X    if (rawbytes) {
X	n = fread(buf, 1, n > rawbytes ? rawbytes : n, ser_rd_fp);
X	rawbytes -= n;
X    } else
X	n = 0;
X
X    /* if no more left, then fetch the end-of-command signature */
X    if (!rawbytes) {
X	char buf[5];	/* "\r\n.\r\n" */
X
X	fread(buf, 1, 5, ser_rd_fp);
X	rawbytes = -1;
X    }
X#ifdef HAS_SIGHOLD
X    sigrelse(SIGINT);
X#endif
X    return n;
X}
X#endif /* USE_XTHREAD */
X
X#endif /* USE_NNTP */
SHAR_EOF
  $shar_touch -am 1118220194 'trn-3.6/nntp.c' &&
  chmod 0644 'trn-3.6/nntp.c' ||
  echo 'restore of trn-3.6/nntp.c failed'
  shar_count="`wc -c < 'trn-3.6/nntp.c'`"
  test 9741 -eq "$shar_count" ||
    echo "trn-3.6/nntp.c: original size 9741, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/cache.c ==============
if test -f 'trn-3.6/cache.c' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/cache.c (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/cache.c (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/cache.c' &&
X/* $Id: cache.c,v 3.0 1992/02/01 03:09:32 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#include "EXTERN.h"
X#include "common.h"
X#include "INTERN.h"
X#include "cache.h"
X#include "EXTERN.h"
X#include "intrp.h"
X#include "search.h"
X#include "ng.h"
X#include "trn.h"
X#include "ngdata.h"
X#include "term.h"
X#include "final.h"
X#include "artsrch.h"
X#include "head.h"
X#include "bits.h"
X#include "rcstuff.h"
X#include "hash.h"
X#include "nntp.h"
X#include "rthread.h"
X#include "rt-ov.h"
X#include "rt-page.h"
X#include "rt-process.h"
X#include "rt-select.h"
X#include "rt-util.h"
X#include "util.h"
X#include "util2.h"
X
X#ifdef PENDING
X#   ifdef ARTSEARCH
X	COMPEX srchcompex;		/* compiled regex for searchahead */
X#   endif
X#endif
X
XHASHTABLE *subj_hash = 0;
XHASHTABLE *shortsubj_hash = 0;
X
Xint subject_cmp _((char *,int,HASHDATUM));
X
Xvoid
Xcache_init()
X{
X#ifdef PENDING
X# ifdef ARTSEARCH
X    init_compex(&srchcompex)
X# endif
X#endif
X    ;
X}
X
XNG_NUM cached_ng = -1;
Xtime_t cached_time = 0;
XART_NUM cached_cnt = 0;
XART_NUM cached_absfirst = 0;
X
Xvoid
Xbuild_cache()
X{
X    if (cached_ng == ng && cached_absfirst == absfirst
X     && time((time_t*)NULL) < cached_time + 6*60*60L) {
X	grow_cache(lastart);
X	rc_to_bits();
X	if (sel_mode == SM_ARTICLE)
X	    set_selector(sel_mode, sel_artsort);
X	else
X	    set_selector(sel_threadmode, sel_threadsort);
X	thread_grow();
X	return;
X    }
X
X    close_cache();
X
X    cached_ng = ng;
X    cached_absfirst = absfirst;
X    cached_time = time((time_t*)NULL);
X    cached_cnt = lastart-absfirst+2 + 10;
X    article_list = (ARTICLE*)
X	safemalloc((MEM_SIZE)(cached_cnt * sizeof (ARTICLE)));
X    bzero((char*)article_list, cached_cnt * sizeof (ARTICLE));
X    subj_hash = hashcreate(201, subject_cmp);	/*TODO: pick a better size */
X
X    rc_to_bits();			/* sets firstart */
X    first_cached = thread_always? absfirst : firstart;
X    last_cached = first_cached-1;
X    cached_all_in_range = FALSE;
X#ifdef PENDING
X    subj_to_get = xref_to_get = firstart;
X#endif
X#ifndef USE_NNTP
X    setmissingbits();
X#endif
X
X    /* Cache as much data in advance as possible, possibly threading
X    ** articles as we go. */
X    thread_open();
X}
X
X#define FIXPTR(p) if ((p) && (p) >= oldlist_start && (p) <= oldlist_end)\
X		      (p) = (article_list + ((p) - oldlist_start)); else
X
Xvoid
Xgrow_cache(newlast)
XART_NUM newlast;
X{
X    ART_NUM new_cnt = newlast-absfirst+2;
X
X    if (new_cnt > cached_cnt) {
X	ARTICLE *oldlist_start = article_list;
X	ARTICLE *oldlist_end = oldlist_start + cached_cnt;
X	new_cnt += 10;
X	article_list = (ARTICLE*)saferealloc((char*)article_list,
X		(MEM_SIZE)(new_cnt * sizeof (ARTICLE)));
X	bzero((char*)(article_list+cached_cnt),
X		(new_cnt-cached_cnt) * sizeof (ARTICLE));
X	if (article_list != oldlist_start) {
X	    register ARTICLE *ap;
X	    register SUBJECT *sp;
X	    for (sp = first_subject; sp; sp = sp->next) {
X		FIXPTR(sp->thread);
X		FIXPTR(sp->articles);
X		if (sp->thread) {
X		    for (ap = sp->thread; ap; ap = bump_art(ap)) {
X			FIXPTR(ap->child1);
X			FIXPTR(ap->parent);
X			FIXPTR(ap->sibling);
X			FIXPTR(ap->subj_next);
X		    }
X		} else {
X		    for (ap = sp->articles; ap; ) {
X			FIXPTR(ap->subj_next);
X			ap = ap->subj_next;
X		    }
X		}
X	    }
X	    FIXPTR(artp);
X	    FIXPTR(curr_artp);
X	    FIXPTR(recent_artp);
X	}
X	cached_cnt = new_cnt;
X    }
X    cached_time = time((time_t*)NULL);
X}
X
Xvoid
Xclose_cache()
X{
X    SUBJECT *sp, *next;
X    ARTICLE *ap;
X    ART_NUM i;
X
X    if (subj_hash) {
X	hashdestroy(subj_hash);
X	subj_hash = 0;
X    }
X    if (shortsubj_hash) {
X	hashdestroy(shortsubj_hash);
X	shortsubj_hash = 0;
X    }
X    /* Free all the subjects. */
X    for (sp = first_subject; sp; sp = next) {
X	next = sp->next;
X	free(sp->str);
X	free((char*)sp);
X    }
X    first_subject = last_subject = Nullsubj;
X    subject_count = 0;			/* just to be sure */
X    parsed_art = 0;
X
X    if (artptr_list) {
X	free((char*)artptr_list);
X	artptr_list = Null(ARTICLE**);
X    }
X    artptr = Null(ARTICLE**);
X    thread_close();
X
X    if (cached_cnt) {
X	for (i = 0, ap = article_list; i < cached_cnt; i++, ap++)
X	    clear_article(ap);
X	free((char*)article_list);
X	cached_cnt = 0;
X    }
X    cached_ng = -1;
X}
X
X/* The article has all it's data in place, so add it to the list of articles
X** with the same subject.
X*/
Xvoid
Xcache_article(ap)
Xregister ARTICLE *ap;
X{
X    register ARTICLE *next, *ap2;
X
X    if (!(next = ap->subj->articles) || ap->date < next->date)
X	ap->subj->articles = ap;
X    else {
X	while ((next = (ap2 = next)->subj_next) && next->date <= ap->date)
X	    ;
X	ap2->subj_next = ap;
X    }
X    ap->subj_next = next;
X    ap->flags |= AF_CACHED;
X
X    if (!(ap->flags & AF_READ) ^ sel_rereading) {
X	if (ap->subj->flags & SF_WASSELECTED)
X	    select_article(ap, 0);
X	else
X	    ap->subj->flags |= SF_VISIT;
X    }
X
X    if (join_subject_len != 0)
X	check_for_near_subj(ap);
X}
X
Xvoid
Xcheck_for_near_subj(ap)
XARTICLE *ap;
X{
X    register SUBJECT *sp;
X    if (!shortsubj_hash) {
X	shortsubj_hash = hashcreate(201, subject_cmp);	/*TODO: pick a better size */
X	sp = first_subject;
X    }
X    else {
X	sp = ap->subj;
X	if (sp->next)
X	    sp = 0;
X    }
X    while (sp) {
X	if (strlen(sp->str+4) >= join_subject_len && sp->thread) {
X	    SUBJECT *sp2;
X	    HASHDATUM data;
X	    data = hashfetch(shortsubj_hash, sp->str+4, join_subject_len);
X	    if (!(sp2 = (SUBJECT*)data.dat_ptr)) {
X		data.dat_ptr = (char*)sp;
X		hashstorelast(data);
X	    }
X	    else if (sp->thread != sp2->thread) {
X		merge_threads(sp2, sp);
X	    }
X	}
X	sp = sp->next;
X    }
X}
X
Xvoid
Xchange_join_subject_len(len)
Xint len;
X{
X    if (join_subject_len != len) {
X	if (shortsubj_hash) {
X	    hashdestroy(shortsubj_hash);
X	    shortsubj_hash = 0;
X	}
X	join_subject_len = len;
X	if (len && first_subject && first_subject->articles)
X	    check_for_near_subj(first_subject->articles);
X    }
X}
X
Xvoid
Xcheck_poster(ap)
Xregister ARTICLE *ap;
X{
X    if (auto_select_postings && !(ap->flags & AF_MISSING) && ap->from) {
X	if (ap->flags & AF_FROMTRUNCED) {
X	    strcpy(cmd_buf,realname);
X	    if (strEQ(ap->from,compress_name(cmd_buf,16))) {
X		untrim_cache = TRUE;
X		fetchfrom(article_num(ap),FALSE);
X		untrim_cache = FALSE;
X	    }
X	}
X	if (!(ap->flags & AF_FROMTRUNCED)) {
X	    char *s = cmd_buf, *u, *h;
X	    strcpy(s,ap->from);
X	    if ((h=index(s,'<')) != Nullch) { /* grab the good part */
X		s = h+1;
X		if ((h=index(s,'>')) != Nullch)
X		    *h = '\0';
X	    } else if ((h=index(s,'(')) != Nullch) {
X		while (h-- != s && *h == ' ')
X		    ;
X		h[1] = '\0';		/* or strip the comment */
X	    }
X	    if ((h=index(s,'%'))!=Nullch || (h=index(s,'@'))!=Nullch) {
X		*h++ = '\0';
X		u = s;
X	    } else if ((u=rindex(s,'!')) != Nullch) {
X		*u++ = '\0';
X		h = s;
X	    } else
X		h = u = s;
X	    if (strEQ(u,loginName)) {
X		if (instr(h,phostname,FALSE)) {
X		    switch (auto_select_postings) {
X		    case '.':
X			select_subthread(ap,AUTO_SELECT);
X			break;
X		    case '+':
X			select_arts_thread(ap,AUTO_SELECT);
X			break;
X		    case 'p':
X			if (ap->parent)
X			    select_subthread(ap->parent,AUTO_SELECT);
X			else
X			    select_subthread(ap,AUTO_SELECT);
X			break;
X		    }
X		} else {
X#ifdef REPLYTO_POSTER_CHECKING
X		    char *reply_buf = fetchlines(article_num(ap),REPLY_LINE);
X		    if (instr(reply_buf,loginName,TRUE))
X			select_subthread(ap,AUTO_SELECT);
X		    free(reply_buf);
X#endif
X		}
X	    }
X	}
X    }
X}
X
X/* The article turned out to be a duplicate, so remove it from the cached
X** list and possibly destroy the subject (should only happen if the data
X** was corrupt and the duplicate id got a different subject).
X*/
Xvoid
Xuncache_article(ap, remove_empties)
Xregister ARTICLE *ap;
Xbool_int remove_empties;
X{
X    register ARTICLE *next;
X
X    if (ap->subj) {
X	if ((ap->flags & (AF_CACHED|AF_MISSING)) == AF_CACHED) {
X	    if ((next = ap->subj->articles) == ap)
X		ap->subj->articles = ap->subj_next;
X	    else {
X		register ARTICLE *ap2;
X		while (next) {
X		    if ((ap2 = next->subj_next) == ap) {
X			next->subj_next = ap->subj_next;
X			break;
X		    }
X		    next = ap2;
X		}
X	    }
X	}
X	if (remove_empties && !ap->subj->articles) {
X	    register SUBJECT *sp = ap->subj;
X	    if (sp == first_subject)
X		first_subject = sp->next;
X	    else
X		sp->prev->next = sp->next;
X	    if (sp == last_subject)
X		last_subject = sp->prev;
X	    else
X		sp->next->prev = sp->prev;
X	    hashdelete(subj_hash, sp->str+4, strlen(sp->str+4));
X	    free((char*)sp);
X	    ap->subj = Nullsubj;
X	    subject_count--;
X	}
X    }
X    onemissing(ap);
X}
X
X/* get the header line from an article's cache or parse the article trying */
X
Xchar *
Xfetchcache(artnum,which_line,fill_cache)
XART_NUM artnum;
Xint which_line;
Xbool_int fill_cache;
X{
X    register char *s;
X    register ARTICLE *ap;
X    register bool cached = (htype[which_line].ht_flags & HT_CACHED);
X
X    /* find_article() returns a Nullart if the artnum value is invalid */
X    if (!(ap = find_article(artnum)) || (ap->flags & AF_MISSING))
X	return nullstr;
X    if (cached && (s=get_cached_line(ap,which_line,untrim_cache)) != Nullch)
X	return s;
X    if (!fill_cache)
X	return Nullch;
X    if (!parseheader(artnum))
X	return nullstr;
X    if (cached)
X	return get_cached_line(ap,which_line,untrim_cache);
X    return Nullch;
X}
X
X/* Return a pointer to a cached header line for the indicated article.
X** Truncated headers (e.g. from a .thread file) are optionally ignored.
X*/
Xchar *
Xget_cached_line(ap, which_line, no_truncs)
Xregister ARTICLE *ap;
Xint which_line;
Xbool_int no_truncs;
X{
X    register char *s;
X
X    switch (which_line) {
X    case SUBJ_LINE:
X	if (!ap->subj || (no_truncs && (ap->subj->flags & SF_SUBJTRUNCED)))
X	    s = Nullch;
X	else
X	    s = ap->subj->str + ((ap->flags & AF_HAS_RE) ? 0 : 4);
X	break;
X    case FROM_LINE:
X	if (no_truncs && (ap->flags & AF_FROMTRUNCED))
X	    s = Nullch;
X	else
X	    s = ap->from;
X	break;
X#ifdef DBM_XREFS
X    case NGS_LINE:
X#else
X    case XREF_LINE:
X#endif
X	s = ap->xrefs;
X	break;
X    case MESSID_LINE:
X	s = ap->msgid;
X	break;
X    default:
X	s = Nullch;
X	break;
X    }
X    return s;
X}
X
Xvoid
Xset_subj_line(ap, subj, size)
Xregister ARTICLE *ap;
Xregister char *subj;	/* not yet allocated, so we can tweak it first */
Xregister int size;
X{
X    HASHDATUM data;
X    SUBJECT *sp;
X    char *newsubj, *subj_start;
X    char *t, *f;
X    int i;
X
X    while (*subj && *subj != '\n' && (unsigned char)*subj <= ' ')
X	subj++;
X    if (subj != (subj_start = get_subject_start(subj))) {
X	if ((size -= subj_start - subj) < 0)
X	    size = 0;
X	ap->flags |= AF_HAS_RE;
X    }
X    if (ap->subj && strnEQ(ap->subj->str+4, subj_start, size))
X	return;
X
X    newsubj = safemalloc(size + 4 + 1);
X    strcpy(newsubj, "Re: ");
X    for (t = newsubj + 4, f = subj_start, i = size; i--; ) {
X	if ((unsigned char)*f <= ' ') {
X	    while (i && ((unsigned char)*++f <= ' '))
X		i--, size--;
X	    *t++ = ' ';
X	} else if (*f != '\n')
X	    *t++ = *f++;
X	else
X	    f++;
X    }
X    while (size > 4 && t[-1] == ' ')
X	t--, size--;
X    *t = '\0';
X
X    if (ap->subj) {
X	/* This only happens when we freshen truncated subjects */
X	hashdelete(subj_hash, ap->subj->str+4, strlen(ap->subj->str+4));
X	free(ap->subj->str);
X	ap->subj->str = newsubj;
X	data.dat_ptr = (char*)ap->subj;
X	hashstore(subj_hash, newsubj + 4, size, data);
X    } else {
X	data = hashfetch(subj_hash, newsubj + 4, size);
X	if (!(sp = (SUBJECT*)data.dat_ptr)) {
X	    sp = (SUBJECT*)safemalloc(sizeof (SUBJECT));
X	    bzero((char*)sp, sizeof (SUBJECT));
X	    subject_count++;
X	    if ((sp->prev = last_subject) != NULL)
X		sp->prev->next = sp;
X	    else
X		first_subject = sp;
X	    last_subject = sp;
X	    sp->str = newsubj;
X	    sp->thread_link = sp;
X	    sp->flags = 0;
X
X	    data.dat_ptr = (char*)sp;
X	    hashstorelast(data);
X	} else
X	    free(newsubj);
X	ap->subj = sp;
X    }
X}
X
Xvoid
Xset_cached_line(ap, which_line, s)
Xregister ARTICLE *ap;
Xregister int which_line;
Xregister char *s;		/* already allocated, ready to save */
X{
X    char *cp;
X    /* SUBJ_LINE is handled specially above */
X    switch (which_line) {
X    case FROM_LINE:
X	ap->flags &= ~AF_FROMTRUNCED;
X	if (ap->from)
X	    free(ap->from);
X	ap->from = s;
X	break;
X#ifdef DBM_XREFS
X    case NGS_LINE:
X	if (ap->xrefs && ap->xrefs != nullstr)
X	    free(ap->xrefs);
X	if (!index(s, ',')) {	/* if no comma, no Xref! */
X	    free(s);
X	    s = nullstr;
X	}
X	ap->xrefs = s;
X	break;
X#else
X    case XREF_LINE:
X	if (ap->xrefs && ap->xrefs != nullstr)
X	    free(ap->xrefs);
X	/* Exclude an xref for just this group or "(none)". */
X	cp = index(s, ':');
X	if (!cp || !index(cp+1, ':')) {
X	    free(s);
X	    s = nullstr;
X	}
X	ap->xrefs = s;
X	break;
X#endif
X    case MESSID_LINE:
X	if (ap->msgid)
X	    free(ap->msgid);
X	ap->msgid = s;
X	break;
X    }
X}
X
Xint
Xsubject_cmp(key, keylen, data)
Xchar *key;
Xint keylen;
XHASHDATUM data;
X{
X    /* We already know that the lengths are equal, just compare the strings */
X    return bcmp(key, ((SUBJECT*)data.dat_ptr)->str+4, keylen);
X}
X
X/* see what we can do while they are reading */
X
X#ifdef PENDING
Xvoid
Xlook_ahead()
X{
X#ifdef ARTSEARCH
X    register char *h, *s;
X
X#ifdef DEBUG
X    if (debug && srchahead) {
X	printf("(%ld)",(long)srchahead);
X	fflush(stdout);
X    }
X#endif
X#endif
X
X    if (ThreadedGroup) {
X	artp = curr_artp;
X	inc_art(selected_only,FALSE);
X	if (artp)
X	    parseheader(art);
X    }
X    else
X#ifdef ARTSEARCH
X    if (srchahead && srchahead < art) {	/* in ^N mode? */
X	char *pattern;
X
X	pattern = buf+1;
X	strcpy(pattern,": *");
X	h = pattern + strlen(pattern);
X	interp(h,(sizeof buf) - (h-buf),"%\\s");
X	{			/* compensate for notesfiles */
X	    register int i;
X	    for (i = 24; *h && i--; h++)
X		if (*h == '\\')
X		    h++;
X	    *h = '\0';
X	}
X#ifdef DEBUG
X	if (debug & DEB_SEARCH_AHEAD) {
X	    fputs("(hit CR)",stdout);
X	    fflush(stdout);
X	    fgets(buf+128, sizeof buf-128, stdin);
X	    printf("\npattern = %s\n",pattern);
X	}
X#endif
X	if ((s = compile(&srchcompex,pattern,TRUE,TRUE)) != Nullch) {
X				    /* compile regular expression */
X	    printf("\n%s\n",s) FLUSH;
X	    srchahead = 0;
X	}
X	if (srchahead) {
X	    srchahead = art;
X	    for (;;) {
X		srchahead++;	/* go forward one article */
X		if (srchahead > lastart) { /* out of articles? */
X#ifdef DEBUG
X		    if (debug)
X			fputs("(not found)",stdout);
X#endif
X		    break;
X		}
X		if (!was_read(srchahead) &&
X		    wanted(&srchcompex,srchahead,0)) {
X				    /* does the shoe fit? */
X#ifdef DEBUG
X		    if (debug)
X			printf("(%ld)",(long)srchahead);
X#endif
X		    parseheader(srchahead);
X		    break;
X		}
X		if (input_pending())
X		    break;
X	    }
X	    fflush(stdout);
X	}
X    }
X    else
X#endif /* ARTSEARCH */
X    {
X	if (art+1 <= lastart)		/* how about a pre-fetch? */
X	    parseheader(art+1);		/* look for the next article */
X    }
X}
X#endif /* PENDING */
X
X/* see what else we can do while they are reading */
X
Xvoid
Xcache_until_key()
X{
X#ifdef PENDING
X    if (!in_ng || input_pending())
X	return;
X
X    untrim_cache = TRUE;
X    sentinel_artp = curr_artp;
X
X#ifdef USE_NNTP
X    if (nntp_finishbody(FB_BACKGROUND))
X	return;
X#endif
X
X    /* Prioritize our caching based on what mode we're in */
X    if (mode == 't') {
X	if (cache_subjects())
X	    if (cache_xrefs())
X		if (chase_xrefs(TRUE))
X		    if (ThreadedGroup)
X			cache_all_arts();
X		    else
X			cache_unread_arts();
X    } else {
X	if (!ThreadedGroup || cache_all_arts())
X	    if (cache_subjects())
X		if (cache_unread_arts())
X		    if (cache_xrefs())
X			chase_xrefs(TRUE);
X    }
X
X    setspin(SPIN_OFF);
X    untrim_cache = FALSE;
X#endif
X}
X
X#ifdef PENDING
Xbool
Xcache_subjects()
X{
X    register ARTICLE *ap;
X    register ART_NUM an;
X
X    if (subj_to_get > lastart)
X	return TRUE;
X    setspin(SPIN_BACKGROUND);
X    for (an = subj_to_get, ap = article_ptr(an); an <= lastart; ap++, an++) {
X	if (input_pending())
X	    break;
X	if (!(ap->flags & AF_READ))
X	    fetchsubj(an,FALSE);
X    }
X    subj_to_get = an;
X    return subj_to_get > lastart;
X}
X
Xbool
Xcache_xrefs()
X{
X    register ARTICLE *ap;
X    register ART_NUM an;
X
X    if (olden_days || xref_to_get > lastart)
X	return TRUE;
X    setspin(SPIN_BACKGROUND);
X    for (an = xref_to_get, ap = article_ptr(an); an <= lastart; ap++, an++) {
X	if (input_pending())
X	    break;
X	if (!(ap->flags & AF_READ))
X	    fetchxref(an,FALSE);
X    }
X    xref_to_get = an;
X    return xref_to_get > lastart;
X}
X
Xbool
Xcache_all_arts()
X{
X    int old_last_cached = last_cached;
X    if (!cached_all_in_range)
X	last_cached = first_cached-1;
X    if (last_cached >= lastart && first_cached <= absfirst)
X	return TRUE;
X
X    /* turn it on as late as possible to avoid fseek()ing openart */
X    setspin(SPIN_BACKGROUND);
X    if (last_cached < lastart) {
X	if (ov_opened)
X	    ov_data(last_cached+1, lastart, TRUE);
X	if (!art_data(last_cached+1, lastart, TRUE, TRUE)) {
X	    last_cached = old_last_cached;
X	    return FALSE;
X	}
X	cached_all_in_range = TRUE;
X    }
X    if (first_cached > absfirst) {
X	if (ov_opened)
X	    ov_data(absfirst, first_cached-1, TRUE);
X	else
X	    art_data(absfirst, first_cached-1, TRUE, TRUE);
X	/* If we got interrupted, make a quick exit */
X	if (first_cached > absfirst) {
X	    last_cached = old_last_cached;
X	    return FALSE;
X	}
X    }
X    /* We're all done threading the group, so if the current article is
X    ** still in doubt, tell them it's missing. */
X    if (curr_artp && !(curr_artp->flags & AF_CACHED) && !input_pending())
X	pushchar('\f' | 0200);
X    /* A completely empty group needs a count & a sort */
X    if (mode != 't' && !article_count && !selected_only)
X	thread_grow();
X    return TRUE;
X}
X
Xbool
Xcache_unread_arts()
X{
X    if (last_cached >= lastart)
X	return TRUE;
X    setspin(SPIN_BACKGROUND);
X    return art_data(last_cached+1, lastart, TRUE, FALSE);
X}
X#endif
X
Xbool
Xart_data(first, last, cheating, all_articles)
XART_NUM first, last;
Xbool_int cheating;
Xbool_int all_articles;
X{
X    register ARTICLE *ap;
X    register ART_NUM i;
X    int cachemask = (ThreadedGroup ? AF_THREADED : AF_CACHED)
X		  + (all_articles? 0 : AF_READ);
X
X    setspin(cheating? SPIN_BACKGROUND : SPIN_FOREGROUND);
X    assert(first >= absfirst && last <= lastart);
X    for (i = first, ap = article_ptr(first); i <= last; i++, ap++) {
X	if (ap->flags & cachemask)
X	    continue;
X	if (int_count) {
X	    int_count = 0;
X	    break;
X	}
X	if (cheating) {
X	    if (input_pending())
X		break;
X	    /* If the current article is no longer a '?', let them know. */
X	    if (curr_artp != sentinel_artp) {
X		pushchar('\f' | 0200);
X		break;
X	    }
X	}
X	/* This parses the header which will cache/thread the article */
X	(void) parseheader(i);
X    }
X    setspin(SPIN_POP);
X    if (--i > last_cached)
X	last_cached = i;
X    if (i == last) {
X	if (first < first_cached)
X	    first_cached = first;
X	return TRUE;
X    }
X    return FALSE;
X}
X
Xbool
Xcache_range(first,last)
XART_NUM first;
XART_NUM last;
X{
X    bool success = TRUE;
X    bool all_arts = (sel_rereading || thread_always);
X    ART_NUM count = 0;
X
X    if (sel_rereading && !cached_all_in_range) {
X	first_cached = first;
X	last_cached = first-1;
X    }
X    if (first < first_cached)
X	count = first_cached-first;
X    if (last > last_cached)
X	count += last-last_cached;
X    if (!count)
X	return TRUE;
X
X    if (first_cached > last_cached) {
X	if (sel_rereading) {
X	    if (first_subject)
X		count -= toread[ng];
X	} else if (first == firstart && last == lastart && !all_arts)
X	    count = toread[ng];
X    }
X
X    printf("\n%sing %ld article%s.", ThreadedGroup? "Thread" : "Cach",
X	   (long)count, count==1? nullstr : "s") FLUSH;
X
X    setspin(SPIN_FOREGROUND);
X
X    if (first < first_cached) {
X	if (ov_opened) {
X	    ov_data(absfirst,first_cached-1,FALSE);
X	    if ((success = (first_cached == absfirst)) != FALSE)
X		ov_close();
X	} else {
X	    success = art_data(first, first_cached-1, FALSE, all_arts);
X	    cached_all_in_range = (all_arts && success);
X	}
X    }
X    if (success && last_cached < last) {
X	if (ov_opened)
X	    ov_data(last_cached+1, last, FALSE);
X	success = art_data(last_cached+1, last, FALSE, all_arts);
X	cached_all_in_range = (all_arts && success);
X    }
X    setspin(SPIN_POP);
X    return success;
X}
X
Xvoid
Xclear_article(ap)
Xregister ARTICLE *ap;
X{
X    if (ap->from)
X	free(ap->from);
X    if (ap->msgid)
X	free(ap->msgid);
X    if (ap->xrefs && ap->xrefs != nullstr)
X	free(ap->xrefs);
X}
SHAR_EOF
  $shar_touch -am 1110023094 'trn-3.6/cache.c' &&
  chmod 0644 'trn-3.6/cache.c' ||
  echo 'restore of trn-3.6/cache.c failed'
  shar_count="`wc -c < 'trn-3.6/cache.c'`"
  test 20441 -eq "$shar_count" ||
    echo "trn-3.6/cache.c: original size 20441, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/only.c ==============
if test -f 'trn-3.6/only.c' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/only.c (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/only.c (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/only.c' &&
X/* $Id: only.c,v 3.0 1991/09/09 20:23:31 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#include "EXTERN.h"
X#include "common.h"
X#include "search.h"
X#include "util.h"
X#include "final.h"
X#include "ngsrch.h"
X#include "INTERN.h"
X#include "only.h"
X
Xvoid
Xonly_init()
X{
X    ;
X}
X
Xvoid
Xsetngtodo(pat)
Xchar *pat;
X{
X#ifdef ONLY
X#ifdef SPEED_OVER_MEM
X    char *s;
X#endif
X
X    if (!*pat)
X	return;
X    if (maxngtodo < NGMAX) {
X	ngtodo[maxngtodo] = savestr(pat);
X#ifdef SPEED_OVER_MEM
X#ifndef lint
X	compextodo[maxngtodo] = (COMPEX*)safemalloc(sizeof(COMPEX));
X#endif /* lint */
X	init_compex(compextodo[maxngtodo]);
X	compile(compextodo[maxngtodo],pat,TRUE,TRUE);
X	if ((s = ng_comp(compextodo[maxngtodo],pat,TRUE,TRUE)) != Nullch) {
X					    /* compile regular expression */
X	    printf("\n%s\n",s) FLUSH;
X	    finalize(1);
X	}
X#endif
X	maxngtodo++;
X    }
X#else
X    notincl("o");
X#endif
X}
X
X/* if command line list is non-null, is this newsgroup wanted? */
X
Xbool
Xinlist(ngnam)
Xchar *ngnam;
X{
X#ifdef ONLY
X    register int i;
X#ifdef SPEED_OVER_MEM
X
X    if (maxngtodo == 0)
X	return TRUE;
X    for (i=0; i<maxngtodo; i++) {
X	if (execute(compextodo[i],ngnam))
X	    return TRUE;
X    }
X    return FALSE;
X#else
X    COMPEX ilcompex;
X    char *s;
X
X    if (maxngtodo == 0)
X	return TRUE;
X    init_compex(&ilcompex);
X    for (i=0; i<maxngtodo; i++) {
X	if ((s = ng_comp(&ilcompex,ngtodo[i],TRUE,TRUE)) != Nullch) {
X					    /* compile regular expression */
X	    printf("\n%s\n",s) FLUSH;
X	    finalize(1);
X	}
X	
X	if (execute(&ilcompex,ngnam) != Nullch) {
X	    free_compex(&ilcompex);
X	    return TRUE;
X	}
X    }
X    free_compex(&ilcompex);
X    return FALSE;
X#endif
X#else
X    return TRUE;
X#endif
X}
X
X#ifdef ONLY
Xvoid
Xend_only()
X{
X    if (maxngtodo) {			/* did they specify newsgroup(s) */
X	int whicharg;
X
X#ifdef VERBOSE
X	IF(verbose)
X	    printf("\nRestriction %s%s removed.\n",ngtodo[0],
X		maxngtodo > 1 ? ", etc." : nullstr) FLUSH;
X	ELSE
X#endif
X#ifdef TERSE
X	    fputs("\nExiting \"only\".\n",stdout) FLUSH;
X#endif
X	for (whicharg = 0; whicharg < maxngtodo; whicharg++) {
X	    free(ngtodo[whicharg]);
X#ifdef SPEED_OVER_MEM
X	    free_compex(compextodo[whicharg]);
X#ifndef lint
X	    free((char*)compextodo[whicharg]);
X#endif /* lint */
X#endif
X	}
X	maxngtodo = 0;
X	emptyOnly = FALSE;
X    }
X}
X#endif
SHAR_EOF
  $shar_touch -am 0711223693 'trn-3.6/only.c' &&
  chmod 0644 'trn-3.6/only.c' ||
  echo 'restore of trn-3.6/only.c failed'
  shar_count="`wc -c < 'trn-3.6/only.c'`"
  test 2817 -eq "$shar_count" ||
    echo "trn-3.6/only.c: original size 2817, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/only.h ==============
if test -f 'trn-3.6/only.h' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/only.h (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/only.h (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/only.h' &&
X/* $Id: only.h,v 3.0 1991/09/09 20:23:31 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#ifndef NBRA
X#include "search.h"
X#endif
X
X#ifdef ONLY
X    EXT char *ngtodo[NGMAX];		/* restrictions in effect */
X#   ifdef SPEED_OVER_MEM
X	EXT COMPEX *compextodo[NGMAX];	/* restrictions in compiled form */
X#   endif
X#endif
X
XEXT int maxngtodo INIT(0);			/*  0 => no restrictions */
X					/* >0 => # of entries in ngtodo */
XEXT bool emptyOnly INIT(FALSE);
X
Xvoid	only_init _((void));
Xbool	inlist _((char*));	/* return TRUE if ngname is in command line list */
X			/* or if there was no list */
Xvoid	setngtodo _((char*));
X#ifdef ONLY
Xvoid	end_only _((void));
X#endif
SHAR_EOF
  $shar_touch -am 0711223693 'trn-3.6/only.h' &&
  chmod 0644 'trn-3.6/only.h' ||
  echo 'restore of trn-3.6/only.h failed'
  shar_count="`wc -c < 'trn-3.6/only.h'`"
  test 1208 -eq "$shar_count" ||
    echo "trn-3.6/only.h: original size 1208, current size $shar_count"
  rm -f _sharnew.tmp
fi
# ============= trn-3.6/rcln.c ==============
if test -f 'trn-3.6/rcln.c' && test X"$1" != X"-c"; then
  echo 'x - skipping trn-3.6/rcln.c (file already exists)'
  rm -f _sharnew.tmp
else
  > _sharnew.tmp
  echo 'x - extracting trn-3.6/rcln.c (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'trn-3.6/rcln.c' &&
X/* $Id: rcln.c,v 3.0 1992/02/01 03:09:32 davison Trn $
X */
X/* This software is Copyright 1991 by Stan Barber. 
X *
X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
X * use this software as long as: there is no monetary profit gained
X * specifically from the use or reproduction of this software, it is not
X * sold, rented, traded or otherwise marketed, and this copyright notice is
X * included prominently in any copy made. 
X *
X * The authors make no claims as to the fitness or correctness of this software
X * for any use whatsoever, and it is provided as is. Any use of this software
X * is at the user's own risk. 
X */
X
X#include "EXTERN.h"
X#include "common.h"
X#include "util.h"
X#include "util2.h"
X#include "rcstuff.h"
X#include "ngdata.h"
X#include "INTERN.h"
X#include "rcln.h"
X
Xvoid
Xrcln_init()
X{
X    ;
X}
X
X#ifdef CATCHUP
Xvoid
Xcatch_up(ngx, leave_count)
XNG_NUM ngx;
Xint leave_count;
X{
X    char tmpbuf[128];
X    
X    if (leave_count) {
X#ifdef VERBOSE
X	IF(verbose)
X	    printf("\nMarking all but %d articles in %s as read.\n",
X		   leave_count,rcline[ngx]) FLUSH;
X	ELSE
X#endif
X#ifdef TERSE
X	    printf("\nAll but %d marked as read.\n",leave_count) FLUSH;
X#endif
X	checkexpired(ngx, getngsize(ngx) - leave_count + 1);
X    }
X    else {
X#ifdef VERBOSE
X	IF(verbose)
X	    printf("\nMarking %s as all read.\n",rcline[ngx]) FLUSH;
X	ELSE
X#endif
X#ifdef TERSE
X	    fputs("\nMarked read\n",stdout) FLUSH;
X#endif
X	sprintf(tmpbuf,"%s: 1-%ld", rcline[ngx],(long)getngsize(ngx));
X	free(rcline[ngx]);
X	rcline[ngx] = savestr(tmpbuf);
X	*(rcline[ngx] + rcnums[ngx] - 1) = '\0';
X	toread[ngx] = TR_NONE;
X    }
X    write_rc();
X}
X#endif
X
X/* add an article number to a newsgroup, if it isn't already read */
X
Xint
Xaddartnum(artnum,ngnam)
XART_NUM artnum;
Xchar *ngnam;
X{
X    register NG_NUM ngnum = find_ng(ngnam);
X    register char *s, *t, *maxt = Nullch;
X    ART_NUM min = 0, max = -1, lastnum = 0;
X    char *mbuf;
X    bool morenum;
X
X    if (!artnum)
X	return 0;
X    if (ngnum == nextrcline || !rcnums[ngnum])
X					/* not found in newsrc? */
X	return 0;
X    if (!abs1st[ngnum])
X#ifndef ANCIENT_NEWS
X					/* now is a good time to trim down */
X	set_toread(ngnum);		/* the list due to expires if we */
X					/* have not yet. */
X#endif
X#ifdef DEBUG
X    if (artnum > ngmax[ngnum] + 100) {	/* allow for incoming articles */
X	printf("\nCorrupt Xref line!!!  %ld --> %s(1..%ld)\n",
X	    artnum,ngnam,
X	    ngmax[ngnum]) FLUSH;
X	paranoid = TRUE;		/* paranoia reigns supreme */
X	return -1;			/* hope this was the first newsgroup */
X    }
X#endif
X
X    if (toread[ngnum] == TR_BOGUS)
X	return 0;
X#ifdef DEBUG
X    if (debug & DEB_XREF_MARKER) {
X	printf("%ld->\n%s%c%s\n",(long)artnum,rcline[ngnum],rcchar[ngnum],
X	  rcline[ngnum] + rcnums[ngnum]) FLUSH;
X    }
X#endif
X    s = rcline[ngnum] + rcnums[ngnum];
X    while (*s == ' ') s++;		/* skip spaces */
X    t = s;
X    while (isdigit(*s) && artnum >= (min = atol(s))) {
X					/* while it might have been read */
X	for (t = s; isdigit(*t); t++) ;	/* skip number */
X	if (*t == '-') {		/* is it a range? */
X	    t++;			/* skip to next number */
X	    if (artnum <= (max = atol(t)))
X		return 0;		/* it is in range => already read */
X	    lastnum = max;		/* remember it */
X	    maxt = t;			/* remember position in case we */
X					/* want to overwrite the max */
X	    while (isdigit(*t)) t++;	/* skip second number */
X	}
X	else {
X	    if (artnum == min)		/* explicitly a read article? */
X		return 0;
X	    lastnum = min;		/* remember what the number was */
X	    maxt = Nullch;		/* last one was not a range */
X	}
X	while (*t && !isdigit(*t)) t++;	/* skip comma and any spaces */
X	s = t;
X    }
X
X    /* we have not read it, so insert the article number before s */
X
X    morenum = isdigit(*s);		/* will it need a comma after? */
X    *(rcline[ngnum] + rcnums[ngnum] - 1) = RCCHAR(rcchar[ngnum]);
X    mbuf = safemalloc((MEM_SIZE)(strlen(s) + (s-rcline[ngnum]) + 6+2+1));
X    strcpy(mbuf,rcline[ngnum]);		/* make new rc line */
X    if (maxt && lastnum && artnum == lastnum+1)
X    					/* can we just extend last range? */
X	t = mbuf + (maxt-rcline[ngnum]);/* then overwrite previous max */
X    else {
X	t = mbuf + (t-rcline[ngnum]);	/* point t into new line instead */
X	if (lastnum) {			/* have we parsed any line? */
X	    if (!morenum)		/* are we adding to the tail? */
X		*t++ = ',';		/* supply comma before */
X	    if (!maxt && artnum == lastnum+1 && *(t-1) == ',')
X					/* adjacent singletons? */
X		*(t-1) = '-';		/* turn them into a range */
X	}
X    }
X    if (morenum) {			/* is there more to life? */
X	if (min == artnum+1) {		/* can we consolidate further? */
X	    bool range_before = (*(t-1) == '-');
X	    bool range_after;
X	    char *nextmax;
X
X	    for (nextmax = s; isdigit(*nextmax); nextmax++) ;
X	    range_after = *nextmax++ == '-';
X	    
X	    if (range_before)
X		*t = '\0';		/* artnum is redundant */
X	    else
X		sprintf(t,"%ld-",(long)artnum);/* artnum will be new min */
X	    
X	    if (range_after)
X		s = nextmax;		/* *s is redundant */
X	/*  else
X		s = s */		/* *s is new max */
X	}
X	else
X	    sprintf(t,"%ld,",(long)artnum);	/* put the number and comma */
X    }
X    else
X	sprintf(t,"%ld",(long)artnum);	/* put the number there (wherever) */
X    strcat(t,s);			/* copy remainder of line */
X#ifdef DEBUG
X    if (debug & DEB_XREF_MARKER) {
X	printf("%s\n",mbuf) FLUSH;
X    }
X#endif
X    free(rcline[ngnum]);
X    rcline[ngnum] = mbuf;		/* pull the switcheroo */
X    *(rcline[ngnum] + rcnums[ngnum] - 1) = '\0';
X					/* wipe out : or ! */
X    if (toread[ngnum] > TR_NONE)	/* lest we turn unsub into bogus */
X	--toread[ngnum];
X    return 0;
X}
X
X#ifdef MCHASE
X/* delete an article number from a newsgroup, if it is there */
X
Xvoid
Xsubartnum(artnum,ngnam)
Xregister ART_NUM artnum;
Xchar *ngnam;
X{
X    register NG_NUM ngnum = find_ng(ngnam);
X    register char *s, *t;
X    register ART_NUM min, max;
X    char *mbuf;
X    int curlen;
X
X    if (!artnum)
X	return;
X    if (ngnum == nextrcline || !rcnums[ngnum])
X	return;				/* not found in newsrc? */
X#ifdef DEBUG
X    if (debug & DEB_XREF_MARKER) {
X	printf("%ld<-\n%s%c%s\n",(long)artnum,rcline[ngnum],rcchar[ngnum],
X	  rcline[ngnum] + rcnums[ngnum]) FLUSH;
X    }
X#endif
X    s = rcline[ngnum] + rcnums[ngnum];
X    while (*s == ' ') s++;		/* skip spaces */
X    
X    /* a little optimization, since it is almost always the last number */
X    
X    for (t=s; *t; t++) ;		/* find end of string */
X    curlen = t-rcline[ngnum];
X    for (t--; isdigit(*t); t--) ;	/* find previous delim */
X    if (*t == ',' && atol(t+1) == artnum) {
X	*t = '\0';
X	if (toread[ngnum] >= TR_NONE)
X	    ++toread[ngnum];
X#ifdef DEBUG
X	if (debug & DEB_XREF_MARKER)
X	    printf("%s%c %s\n",rcline[ngnum],rcchar[ngnum],s) FLUSH;
X#endif
X	return;
X    }
X
X    /* not the last number, oh well, we may need the length anyway */
X
X    while (isdigit(*s) && artnum >= (min = atol(s))) {
X					/* while it might have been read */
X	for (t = s; isdigit(*t); t++) ;	/* skip number */
X	if (*t == '-') {		/* is it a range? */
X	    t++;			/* skip to next number */
X	    max = atol(t);
X	    while (isdigit(*t)) t++;	/* skip second number */
X	    if (artnum <= max) {
X					/* it is in range => already read */
X		if (artnum == min) {
X		    min++;
X		    artnum = 0;
X		}
X		else if (artnum == max) {
X		    max--;
X		    artnum = 0;
X		}
X		*(rcline[ngnum] + rcnums[ngnum] - 1) = RCCHAR(rcchar[ngnum]);
X		mbuf = safemalloc((MEM_SIZE)(curlen + (artnum?(6+1)*2+1:1+1)));
X		*s = '\0';
X		strcpy(mbuf,rcline[ngnum]);	/* make new rc line */
X		s = mbuf + (s-rcline[ngnum]);
X					/* point s into mbuf now */
X		if (artnum) {		/* split into two ranges? */
X		    prange(s,min,artnum-1);
X		    s += strlen(s);
X		    *s++ = ',';
X		    prange(s,artnum+1,max);
X		}
X		else			/* only one range */
X		    prange(s,min,max);
X		strcat(s,t);		/* copy remainder over */
X#ifdef DEBUG
X		if (debug & DEB_XREF_MARKER) {
X		    printf("%s\n",mbuf) FLUSH;
X		}
X#endif
X		free(rcline[ngnum]);
X		rcline[ngnum] = mbuf;	/* pull the switcheroo */
X		*(rcline[ngnum] + rcnums[ngnum] - 1) = '\0';
X					/* wipe out : or ! */
X		if (toread[ngnum] >= TR_NONE)
X		    ++toread[ngnum];
X		return;
X	    }
X	}
X	else {
X	    if (artnum == min) {	/* explicitly a read article? */
X		if (*t == ',')		/* pick a comma, any comma */
X		    t++;
X		else if (s[-1] == ',')
X		    s--;
X		else if (s[-2] == ',')	/* (in case of space) */
X		    s -= 2;
X		strcpy(s,t);		/* no need to realloc */
X		if (toread[ngnum] >= TR_NONE)
X		    ++toread[ngnum];
X#ifdef DEBUG
X		if (debug & DEB_XREF_MARKER) {
X		    printf("%s%c%s\n",rcline[ngnum],rcchar[ngnum],
X		      rcline[ngnum] + rcnums[ngnum]) FLUSH;
X		}
X#endif
X		return;
X	    }
X	}
X	while (*t && !isdigit(*t)) t++;	/* skip comma and any spaces */
X	s = t;
X    }
X}
X
Xvoid
Xprange(where,min,max)
Xchar *where;
XART_NUM min,max;
X{
X    if (min == max)
X	sprintf(where,"%ld",(long)min);
X    else
X	sprintf(where,"%ld-%ld",(long)min,(long)max);
X}
X#endif
X
X/* calculate the number of unread articles for a newsgroup */
X
Xvoid
Xset_toread(ngnum)
Xregister NG_NUM ngnum;
X{
X    register char *s, *c, *h;
X    char tmpbuf[64], *mybuf = tmpbuf;
X    char *nums;
X    int length;
X    bool virgin_ng = (!abs1st[ngnum]);
X    ART_NUM ngsize = getngsize(ngnum);
X    ART_NUM unread = ngsize;
X    ART_NUM newmax;
X
X    if (ngsize == TR_BOGUS) {
X	printf("\nWarning!  Bogus newsgroup: %s\n",rcline[ngnum]) FLUSH;
X	paranoid = TRUE;
X	toread[ngnum] = TR_BOGUS;
X	return;
X    }
X    if (virgin_ng) {
X	sprintf(tmpbuf," 1-%ld",(long)ngsize);
X	if (strNE(tmpbuf,rcline[ngnum]+rcnums[ngnum]))
X	    checkexpired(ngnum,abs1st[ngnum]);	/* this might realloc rcline */
X    }
X    nums = rcline[ngnum]+rcnums[ngnum];
X    length = strlen(nums);
X    if (length+5+1 > sizeof tmpbuf)
X	mybuf = safemalloc((MEM_SIZE)(length+5+1));
X    strcpy(mybuf,nums);
X    mybuf[length++] = ',';
X    mybuf[length] = '\0';
X    for (s = mybuf; isspace(*s); s++)
X	    ;
X    for ( ; (c = index(s,',')) != Nullch ; s = ++c) {
X				    /* for each range */
X	*c = '\0';			/* keep index from running off */
X	if ((h = index(s,'-')) != Nullch)	/* find - in range, if any */
X	    unread -= (newmax = atol(h+1)) - atol(s) + 1;
X	else if (newmax = atol(s))
X	    unread--;		/* recalculate length */
X	if (newmax > ngsize) {	/* paranoia check */
X	    if (newmax > ngsize + 100) {
X		unread = -1;
X		break;
X	    } else {
X		unread += newmax - ngsize;
X		ngmax[ngnum] = ngsize = newmax;
X	    }
X	}
X    }
X    if (unread >= 0)		/* reasonable number? */
X	toread[ngnum] = (ART_UNREAD)unread;
X					/* remember how many are left */
X    else {				/* SOMEONE RESET THE NEWSGROUP!!! */
X	toread[ngnum] = (ART_UNREAD)ngsize;
X					/* assume nothing carried over */
X	printf("\nWarning!  Somebody reset %s--assuming nothing read.\n",
X	    rcline[ngnum]) FLUSH;
X	*(rcline[ngnum] + rcnums[ngnum]) = '\0';
X	paranoid = TRUE;		/* enough to make a guy paranoid */
X    }
X    if (mybuf != tmpbuf)
X	free(mybuf);
X    if (rcchar[ngnum] == NEGCHAR)
X	toread[ngnum] = TR_UNSUB;
X}
X
X/* make sure expired articles are marked as read */
X
Xvoid
Xcheckexpired(ngnum,a1st)
Xregister NG_NUM ngnum;
Xregister ART_NUM a1st;
X{
X    register char *s;
X    register ART_NUM num, lastnum = 0;
X    char *mbuf, *newnum;
X
X    if (a1st<=1)
X	return;
X#ifdef DEBUG
X    if (debug & DEB_XREF_MARKER) {
X	printf("1-%ld->\n%s%c%s\n",(long)(a1st-1),rcline[ngnum],rcchar[ngnum],
X	  rcline[ngnum] + rcnums[ngnum]) FLUSH;
X    }
X#endif
X    for (s = rcline[ngnum] + rcnums[ngnum]; isspace(*s); s++);
X    while (*s && (num = atol(s)) <= a1st) {
X	while (isdigit(*s)) s++;
X	while (*s && !isdigit(*s)) s++;
X	lastnum = num;
X    }
X    if (*s) {
X	if (s[-1] == '-') {			/* landed in a range? */
X	    if (lastnum != 1) {
X		if (3 + strlen(s) > strlen(rcline[ngnum]+rcnums[ngnum])) {
X		    mbuf = safemalloc((MEM_SIZE)(rcnums[ngnum] + 3 +
X			strlen(s) + 1));
X		    strcpy(mbuf, rcline[ngnum]);
X		    sprintf(mbuf+rcnums[ngnum]," 1-%s",s);
X		    free(rcline[ngnum]);
X		    rcline[ngnum] = mbuf;
X		} else {
X		    sprintf(rcline[ngnum]+rcnums[ngnum]," 1-%s",s);
X		}
X	    }
X	    goto ret;
X	}
X    }
X    /* s now points to what should follow first range */
X    if (s - rcline[ngnum] > rcnums[ngnum] + 6+4+1)
X	mbuf = rcline[ngnum];
X    else {
X	mbuf = safemalloc((MEM_SIZE)(rcnums[ngnum] + strlen(s) + 6+4+1));
X	strcpy(mbuf,rcline[ngnum]);
SHAR_EOF
  : || echo 'restore of trn-3.6/rcln.c failed'
fi
echo 'End of archive part 6'
echo 'File trn-3.6/rcln.c is continued in part 7'
echo 7 > _sharseq.tmp
exit 0


