From: frank.kaefer@stasys.sta.sub.org (Frank Kaefer)
Newsgroups: de.comp.sources.os9,comp.os.os9
Subject: Tass for OS-9 Part01/03
Date: 24 Dec 92 13:05:40 GMT
Followup-To: de.comp.sources.d
Organization: Stasys News Server, Starnberg, Germany
NNTP-Posting-Host: stasys.sta.sub.org

Submitted-by: Ulrich Dessauer <ud@Nightmare.ddt.sub.org>
Archive-name: tass/part01

: ----- Cut here ----- Cut here ----- Cut here ----- Cut here -----
: Use  sh filename  to extract shell archive
: This shell archive contains following files:
:     'MANIFEST                                      660 bytes'
:     'COPYRIGHT                                     356 bytes'
:     'Makefile                                      679 bytes'
:     'art.c                                       16520 bytes'
:     'curses.c                                     8952 bytes'
:     'group.c                                     16732 bytes'
: 
if test -f 'MANIFEST' ; then
  echo 'File MANIFEST already exists, overwriting it'
  del 'MANIFEST'
fi
echo Extracting \"'MANIFEST'\"
sed "s/^X//" >'MANIFEST' <<'__END__OF__THIS__FILE__'
XFilename                  Part   Description
X----------------------------------------------------
XMANIFEST                  1      This shipping list
XCOPYRIGHT                 1      
XMakefile                  1      
Xart.c                     1      
Xcurses.c                  1      
Xgroup.c                   1      
Xmail.c                    2      
Xmain.c                    2      
Xmisc.c                    2      
Xpage.c                    2      
Xprompt.c                  3      
Xscreen.c                  3      
Xselect.c                  3      
Xsigs.c                    3      
Xtass.h                    3      
Xtime.c                    3      
__END__OF__THIS__FILE__
if test -f 'COPYRIGHT' ; then
  echo 'File COPYRIGHT already exists, overwriting it'
  del 'COPYRIGHT'
fi
echo Extracting \"'COPYRIGHT'\"
sed "s/^X//" >'COPYRIGHT' <<'__END__OF__THIS__FILE__'
X/*
X *  Tass, a visual Usenet news reader
X *  (c) Copyright 1990 by Rich Skrenta
X *
X *  Distribution agreement:
X *
X *	You may freely copy or redistribute this software, so long
X *	as there is no profit made from its use, sale, trade or
X *	reproduction.  You may not change this copyright notice,
X *	and it must be included prominently in any copy made.
X */
__END__OF__THIS__FILE__
if test -f 'Makefile' ; then
  echo 'File Makefile already exists, overwriting it'
  del 'Makefile'
fi
echo Extracting \"'Makefile'\"
sed "s/^X//" >'Makefile' <<'__END__OF__THIS__FILE__'
XG=	-g
XCC=	cc
XCFLAGS=	-qt=/dd/TMP $G -DREGEXP -DAPPEND_SIG -DMNEWS -DINCSTR \
X	-DLOCK_INDEX -DRAND_SIG
XLIBS=	-l=/dd/LIB/8bit.l -l=/dd/LIB/termlib.l -l=/dd/LIB/os9lib.l
X
X
XOBJECTS	=	curses.r art.r group.r mail.r main.r misc.r page.r \
X		prompt.r screen.r select.r time.r sigs.r
X
X/h0/ETC/CMDS/tass: $(OBJECTS)
X	$(CC) $(CFLAGS) -m=20 -f=$@ $(OBJECTS) $(LIBS)
X
Xshar:
X	-mv -f ../tass.shar ../tass.shar-
X	shar -v [A-Z]* *.[ch] > ../tass.shar
X
X
Xart.r:		art.c tass.h
Xcurses.r:	curses.c
Xgroup.r:	group.c tass.h
Xmail.r:		mail.c
Xmain.r:		main.c tass.h
Xmisc.r:		misc.c tass.h
Xpage.r:		page.c tass.h
Xprompt.r:	prompt.c tass.h
Xscreen.r:	screen.c tass.h
Xselect.r:	select.c tass.h
Xtime.r:		time.c
__END__OF__THIS__FILE__
if test -f 'art.c' ; then
  echo 'File art.c already exists, overwriting it'
  del 'art.c'
fi
echo Extracting \"'art.c'\"
sed "s/^X//" >'art.c' <<'__END__OF__THIS__FILE__'
X
X
X#include	<stdio.h>
X#include	<time.h>
X#include	<signal.h>
X#include	<errno.h>
X#include	"tass.h"
X
X
X/* Hopefully one of these is right for you. */
X
X#ifdef BSD
X#	include <sys/types.h>
X#	include <sys/dir.h>
X#	define		DIR_BUF		struct direct
X#	define		D_LENGTH	d_namlen
X#endif
X#ifdef M_XENIX
X#	include <sys/ndir.h>
X#	define		DIR_BUF		struct direct
X#	define		D_LENGTH	d_namlen
X#endif
X#ifdef	OSK
X#	include <ctype.h>
X#	include <modes.h>
X#	include <dir.h>
X#	define		DIR_BUF		struct direct
X#	define		D_LENGTH	d_namlen
X#	undef		SPOOLDIR
X#	define		SPOOLDIR	spooldir
X#endif	/* OSK */
X#ifndef DIR_BUF
X#	include	<sys/types.h>
X#	include	<dirent.h>
X#	define		DIR_BUF		struct dirent
X#	define		D_LENGTH	d_reclen
X#endif
X
Xextern char	*index ();
Xextern char	*realloc ();
X
Xchar index_file[LEN+1];
Xchar *glob_art_group;
X
Xstatic int	complete_count;
Xstatic int	complete_expire;
X
X#ifdef SIGTSTP
Xvoid
Xart_susp(i)
Xint i;
X{
X
X	Raw(FALSE);
X	putchar('\n');
X	signal(SIGTSTP, SIG_DFL);
X	kill(0, SIGTSTP);
X
X	signal(SIGTSTP, art_susp);
X	Raw(TRUE);
X
X	mail_setup();
X	ClearScreen();
X	MoveCursor(LINES, 0);
X	printf("Group %s...    ", glob_art_group);
X	fflush(stdout);
X}
X#endif
X
X
X/*
X *  Convert a string to a long, only look at first n characters
X */
X
Xmy_atol(s, n)
Xchar *s;
Xint n;
X{
X	long ret = 0;
X
X	while (*s && n--) {
X		if (*s >= '0' && *s <= '9')
X			ret = ret * 10 + (*s - '0');
X		else
X			return -1;
X		s++;
X	}
X
X	return ret;
X}
X
X# ifdef		LOCK_INDEX
X/*
X *	Lock the index when modifieng it (only on global indicies)
X */
Xstatic char *
Xlock_filename (fle)
Xchar *fle;
X{
X#ifndef	OSK
X	static int	siz = -1;
X	static char	*fname = NULL;
X	int		len;
X
X	if ((len = strlen (fle)) > siz) {
X		siz = len;
X		if (! (fname = realloc (fname, siz + 16)))
X			return (NULL);
X	}
X	sprintf (fname, "%s_lock", fle);
X	return (fname);
X#else
X	static char	fname[64];
X	unsigned long	n;
X	char		*ptr;
X
X	for (ptr = fle, n = 0; *ptr; n += *ptr++ & 0xff)
X		;
X	sprintf (fname, "/dd/TMP/%u_lock", n);
X	return (fname);
X#endif
X}
X
X# ifdef		OSK
Xstatic void
Xstop_signals ()
X{
X	sigmask (1);
X}
X
Xstatic void
Xstart_signals ()
X{
X	sigmask (-1);
X}
X# else		/* OSK */
Xtypedef int	SIGTYPE;
X
Xstatic SIGTYPE	(*signals)()[NSIG];
X
Xstatic void
Xstop_signals ()
X{
X	int	t;
X
X	for (t = 0; t < NSIG; ++t)
X		signals[t] = signal (t, SIG_IGN);
X}
X
Xstatic void
Xstart_signals ()
X{
X	int	t;
X
X	for (t = 0; t < NSIG; ++t)
X		signal (t, signals[t]);
X}
X# endif		/* OSK */
X
Xstatic int	lock_fd = -1;
X
Xstatic int
Xlock_index (fle)
Xchar *fle;
X{
X	int		tout;
X	char		*fn;
X
X	if (lock_fd != -1)
X		close (lock_fd);
X	if (! (fn = lock_filename (fle)))
X		return (-1);
X	for (tout = 20; tout > 0; --tout) {
X# ifndef	OSK
X		if ((lock_fd = creat (fn, 0444)) == -1)
X# else		/* OSK */
X		unlink (fn);
X		if (((lock_fd = create (fn, S_IWRITE, 033)) == -1) && (errno == E_CEF))
X# endif		/* OSK */
X			sleep (1);
X		else
X			break;
X	}
X	if (lock_fd != -1)
X		stop_signals ();
X	return (lock_fd == -1 ? -1 : 0);
X}
X
Xstatic void
Xunlock_index (fle)
Xchar *fle;
X{
X	char	*fn;
X
X	if (lock_fd != -1) {
X		close (lock_fd);
X		lock_fd = -1;
X		if (fn = lock_filename (fle)) {
X# ifndef	OSK
X			chmod (fn, 0666);
X# endif		/* OSK */
X			unlink (fn);
X		}
X		start_signals ();
X	}
X}
X# endif		/* LOCK_INDEX */
X
X/*
X *  Construct the pointers to the basenotes of each thread
X *  arts[] contains every article in the group.  inthread is
X *  set on each article that is after the first article in the
X *  thread.  Articles which have been expired have their thread
X *  set to -2.
X */
X
Xfind_base() {
X	int i;
X
X	top_base = 0;
X
X	for (i = 0; i < top; i++)
X		if (!arts[i].inthread && arts[i].thread != -2) {
X			if (top_base >= max_art)
X				expand_art();
X			base[top_base++] = i;
X		}
X}
X
X
X/* 
X *  Count the number of non-expired articles in arts[]
X */
X
Xnum_arts() {
X	int sum = 0;
X	int i;
X
X	for (i = 0; i < top; i++)
X		if (arts[i].thread != -2)
X			sum++;
X
X	return sum;
X}
X
X
X/*
X *  Do we have an entry for article art?
X */
X
Xvalid_artnum(art)
Xlong art;
X{
X	int i;
X
X	for (i = 0; i < top; i++)
X		if (arts[i].artnum == art)
X			return i;
X
X	return -1;
X}
X
X
X/*
X *  Return TRUE if arts[] contains any expired articles
X *  (articles we have an entry for which don't have a corresponding
X *   article file in the spool directory)
X */
X
Xpurge_needed() {
X	int i;
X
X	for (i = 0; i < top; i++)
X		if (arts[i].thread == -2)
X			return TRUE;
X
X	return FALSE;
X}
X
X
X/*
X *  Main group indexing routine.  Group should be the name of the
X *  newsgroup, i.e. "comp.unix.amiga".  group_path should be the
X *  same but with the .'s turned into /'s: "comp/unix/amiga"
X *
X *  Will read any existing index, create or incrementally update
X *  the index by looking at the articles in the spool directory,
X *  and attempt to write a new index if necessary.
X */
X
Xindex_group(group, group_path)
Xchar *group;
Xchar *group_path;
X{
X	int modified;
X
X	glob_art_group = group;
X
X#ifdef SIGTSTP
X	signal(SIGTSTP, art_susp);
X#endif
X
X	if (!update) {
X		clear_message();
X		MoveCursor(LINES, 0);
X		printf("Group %s...    ", group);
X		fflush(stdout);
X	}
X
X	if (local_index)
X		find_local_index(group);
X	else
X		sprintf(index_file, "%s/%s/.tindex", SPOOLDIR, group_path);
X
X#ifdef	LOCK_INDEX
X	if (! local_index)
X		if (lock_index (index_file) < 0)
X			return -1;
X#endif	/* LOCK_INDEX */
X
X	load_index();
X	modified = read_group(group_path);
X	make_threads();
X	if (modified || purge_needed()) {
X#ifdef	USE_UID
X		if (local_index) {	/* writing index in home directory */
X			setuid(real_uid);	/* so become them */
X			setgid(real_gid);
X		}
X#endif	/* USE_UID */
X		dump_index(group);
X#ifdef	USE_UID
X		if (local_index) {
X			setuid(tass_uid);
X			setgid(tass_gid);
X		}
X#endif	/* USE_UID */
X	}
X
X#ifdef	LOCK_INDEX
X	if (! local_index)
X		unlock_index (index_file);
X#endif	/* LOCK_INDEX */
X
X	find_base();
X
X	if (modified && !update)
X		clear_message();
X}
X
X
X/*
X *  Longword comparison routine for the qsort()
X */
X
Xbase_comp(a, b)
Xlong *a;
Xlong *b;
X{
X
X	if (*a < *b)
X		return -1;
X	if (*a > *b)
X		return 1;
X	return 0;
X}
X
X
X/*
X *  Read the article numbers existing in a group's spool directory
X *  into base[] and sort them.  base_top is one past top.
X */
X
Xscan_dir(group)
Xchar *group;
X{
X	DIR *d;
X	DIR_BUF *e;
X	long art;
X	char buf[200];
X
X	top_base = 0;
X
X	sprintf(buf, "%s/%s", SPOOLDIR, group);
X
X	d = opendir(buf);
X	if (d != NULL) {
X		while ((e = readdir(d)) != NULL) {
X#ifdef		OSK
X			e->D_LENGTH = strlen (e->d_name);
X#endif		/* OSK */
X			art = my_atol(e->d_name, e->D_LENGTH);
X			if (art >= 0) {
X				if (top_base >= max_art)
X					expand_art();
X				base[top_base++] = art;
X			}
X		}
X		closedir(d);
X	}
X
X	qsort(base, top_base, sizeof(long), base_comp);
X}
X
X
X/*
X *  Index a group.  Assumes any existing index has already been
X *  loaded.
X */
X
Xread_group(group)
Xchar *group;
X{
X	char buf[200];
X	int fd;
X	long art;
X	int count;
X	int modified = FALSE;
X	int respnum;
X	int i;
X
X	scan_dir(group);	/* load article numbers into base[] */
X
X	count = 0;
X
X	for (i = 0; i < top_base; i++) {	/* for each article # */
X		art = base[i];
X
X/*
X *  Do we already have this article in our index?  Change thread from
X *  -2 to -1 if so and skip the header eating.
X */
X
X		if ((respnum = valid_artnum(art)) >= 0) {
X			arts[respnum].thread = -1;
X			arts[respnum].unread = 1;
X			continue;
X		}
X
X		if (!modified) {
X			modified = TRUE;   /* we've modified the index */
X					   /* it will need to be re-written */
X#if 0
X			if (!update) {
X				MoveCursor(LINES, 0);
X				fputs("Indexing...    ", stdout);
X				fflush(stdout);
X			}
X#endif
X		}
X
X/*
X *  Add article to arts[]
X */
X		if (top >= max_art)
X			expand_art();
X
X		arts[top].artnum = art;
X		arts[top].thread = -1;
X		arts[top].inthread = FALSE;
X		arts[top].unread = 1;
X
X		sprintf(buf, "%s/%s/%ld", SPOOLDIR, group, art);
X#ifndef	MNEWS
X		fd = open(buf, 0);
X#else	/* MNEWS */
X#ifndef	OSK
X		if ((fd = open (buf, O_RDONLY)) != -1) {
X#else	/* OSK */
X		if ((fd = open (buf, S_IREAD)) != -1) {
X#endif	/* OSK */
X			int	n;
X			n = readln (fd, buf, 180);
X			lseek (fd, 0, 0);
X			if (n > 0) {
X				buf[n - 1] = '\0';
X				if (!strncmp (buf, "%(#)$ ", 6)) {
X					char	sav[200];
X					strcpy (sav, buf + 6);
X					sprintf (buf, "%s/%s", spooldir, sav);
X					close (fd);
X#ifndef	OSK
X					fd = open (buf, O_RDONLY);
X#else	/* OSK */
X					fd = open (buf, S_IREAD);
X#endif	/* OSK */
X				}
X			}
X		}
X#endif	/* MNEWS */
X
X		if (fd < 0) {
X			fprintf(stderr, "can't open article %s: ", buf);
X			perror("");
X			continue;
X		}
X
X		if (!parse_headers(fd, &arts[top]))
X			continue;
X		top++;
X		close(fd);
X
X		if (++count % 10 == 0 && !update) {
X			printf("\b\b\b\b%4d", count);
X			fflush(stdout);
X		}
X	}
X
X	complete_count += count;
X
X	return modified;
X}
X
X
X/*
X *  Go through the articles in arts[] and use .thread to snake threads
X *  through them.  Use the subject line to construct threads.  The
X *  first article in a thread should have .inthread set to FALSE, the
X *  rest TRUE.  Only do unexprired articles we haven't visited yet
X *  (arts[].thread == -1).
X */
X
Xmake_threads() {
X	int i;
X	int j;
X
X	for (i = 0; i < top; i++) {
X		if (arts[i].thread == -1)
X		    for (j = i+1; j < top; j++)
X			if (arts[i].hash == arts[j].hash
X			&&  arts[j].thread != -2
X			&&  strncmp(arts[i].nore, arts[j].nore, 10) == 0) {
X				arts[i].thread = j;
X				arts[j].inthread = TRUE;
X				break;
X			}
X	}
X}
X
X
X/*
X *  Return a pointer into s eliminating any leading Re:'s.  Example:
X *
X *	  Re: Reorganization of misc.jobs
X *	  ^   ^
X */
X
Xchar *
Xeat_re(s)
Xchar *s;
X{
X
X#if 1
X	while (*s == 'r' || *s == 'R') {
X		if (*(s+1) == 'e' || *(s+1) == 'E') {
X			if (*(s+2) == ':')
X				s += 3;
X			else if ((*(s+2) == '^') && isdigit (*(s+3))) {
X				s += 4;
X				if (*s == ':')
X					++s;
X			} else
X				break;
X		} else
X			break;
X		while (isspace (*s))
X			++s;
X	}
X#else
X	while (*s == 'R') {
X		if (strncmp(s, "Re: ", 4) == 0)
X			s += 4;
X		else if (strncmp(s, "Re:", 3) == 0)
X			s += 3;
X		else if (strncmp(s, "Re^2: ", 6) == 0)
X			s += 6;
X		else
X			break;
X	}
X#endif
X
X	return s;
X}
X
X
X/*
X *  Hash the subjects (after eating the Re's off) for a quicker
X *  thread search later.  We store the hashes for subjects in the
X *  index file for speed.
X */
X
Xlong
Xhash_s(s)
Xchar *s;
X{
X	long h = 0;
X
X	while (*s)
X		h = h + (*s++ & 0xff);
X
X	return h;
X}
X
X
Xparse_headers(fd, h)
Xint fd;
Xstruct header *h;
X{
X	char buf[1024];
X	char *p, *q;
X	char flag;
X	char *ptr;
X	int found_from, found_subj;
X	int n;
X
X	if ((n = read(fd, buf, 1024)) <= 0)
X		return FALSE;
X
X	buf[n > 1023 ? 1023 : n] = '\0';
X
X	p = buf;
X	while (p = index (p, '\n'))
X		if (*(p + 1) == '\n') {
X			*p = '\0';
X			break;
X		} else
X			++p;
X
X	p = buf;
X
X	h->from[0] = '\0';
X	h->subject[0] = '\0';
X	h->nore = h->subject;
X	h->hash = 0;
X
X	found_from = FALSE;
X	found_subj = FALSE;
X	while (1) {
X		q = p;
X		while (*p && *p != '\n') {
X			if ((unsigned char) *p & 0x7F < 32)
X				*p = ' ';
X			p++;
X		}
X		flag = *p;
X		*p++ = '\0';
X
X		if ((!found_from) && (strncmp(q, "From: ", 6) == 0)) {
X			if (ptr = index (&q[6], '('))
X				++ptr;
X			else
X				ptr = &q[6];
X			strncpy (h->from, ptr, MAX_FROM);
X			h->from[MAX_FROM-1] = '\0';
X			if (ptr = index (h->from, ')'))
X				*ptr = '\0';
X		} else if ((!found_subj) && (strncmp(q, "Subject: ", 9) == 0)) {
X			h->hash = hash_s(eat_re(&q[9]));
X			strncpy(h->subject, &q[9], MAX_SUBJ);
X			h->subject[MAX_SUBJ-1] = '\0';
X			h->nore = eat_re(h->subject);
X		}
X
X		if ((!flag) || (found_from && found_subj))
X			break;
X	}
X
X	return TRUE;
X}
X
X
X/* 
X *  Write out a .tindex file.  Write the group name first so if
X *  local indexing is done we can disambiguate between group name
X *  hash collisions by looking at the index file.
X */
Xvoid
Xdump_index(group)
Xchar *group;
X{
X	int i;
X	char buf[200];
X	FILE *fp;
X	char *p, *q;
X	long l;
X#ifndef	USE_UID
X	char lockfn[64];
X	int lockfd;
X
X	if (! local_index) {
X#ifndef	OSK
X		sprintf (lockfn, "/tmp/%-.10s", group);
X		lockfd = creat (lockfn, 0, 0444);
X#else	/* OSK */
X		sprintf (lockfn, "/dd/TMP/%-.20s", group);
X		for (p = lockfn; *p; ++p)
X			if (((unsigned char) *p >= 0x80) || ((!isalnum (*p)) && (*p != '/')))
X				*p = '_';
X		lockfd = create (lockfn, 0, 033);
X#endif	/* OSK */
X		if (lockfd < 0) {
X			sleep (1);
X			return;
X		}
X	}
X#endif	/* USE_UID */
X
X	fp = fopen(index_file, "w");
X	if (fp == NULL)
X#ifndef	USE_UID
X		goto dump_index_finish;
X#else	/* USE_UID */
X		return;
X#endif	/* USE_UID */
X
X	fprintf(fp, "%s\n", group);
X	fprintf(fp, "%d\n", num_arts());
X	for (i = 0; i < top; i++)
X	    if (arts[i].thread != -2) {
X		p = arts[i].nore;
X		q = arts[i].subject;
X		l = p - q;
X		fprintf(fp, "%ld\n%s\n%s\n%ld\n%ld\n",
X				arts[i].artnum,
X				arts[i].subject,
X				arts[i].from,
X				arts[i].hash,
X#if 0
X				(long) arts[i].nore - (long) arts[i].subject);
X#else
X				l);
X#endif
X	} else
X		++complete_expire;
X
X	fclose(fp);
X#ifdef	USE_UID
X#ifndef	OSK
X	chmod(index_file, 0644);
X#else	/* OSK */
X	chmod(index_file, 013);
X#endif	/* OSK */
X#else	/* USE_UID */
Xdump_index_finish:
X	if (local_index)
X#ifndef	OSK
X		chmod(index_file, 0644);
X#else	/* OSK */
X		chmod(index_file, 013);
X#endif	/* OSK */
X	else if (lockfd != -1) {
X		close (lockfd);
X#ifndef	OSK
X		chmod (lockfn, 0666);
X#endif	/* OSK */
X		unlink (lockfn);
X#ifndef	OSK
X		chmod(index_file, 0666);
X#else	/* OSK */
X		chmod(index_file, 033);
X#endif	/* OSK */
X	}
X#endif	/* USE_UID */
X}
X
X
X/*
X *  strncpy that stops at a newline and null terminates
X */
X
Xmy_strncpy(p, q, n)
Xchar *p;
Xchar *q;
Xint n;
X{
X
X	while (n--) {
X		if (!*q || *q == '\n')
X			break;
X		*p++ = *q++;
X	}
X	*p = '\0';
X}
X
X
X/*
X *  Read in a .tindex file.
X */
Xvoid
Xload_index()
X{
X	int i;
X	long j;
X	char buf[200];
X	FILE *fp;
X	int first = TRUE;
X
X	top = 0;
X
X	fp = fopen(index_file, "r");
X	if (fp == NULL)
X		return;
X
X	if (fgets(buf, 200, fp) == NULL
X	||  fgets(buf, 200, fp) == NULL) {
X		fprintf(stderr, "one\n");
X		goto corrupt_index;
X	}
X
X	i = atol(buf);
X	while (top < i) {
X		if (top >= max_art)
X			expand_art();
X
X		arts[top].thread = -2;
X		arts[top].inthread = FALSE;
X
X		if (fgets(buf, 200, fp) == NULL) {
X			fprintf(stderr, "two\n");
X			goto corrupt_index;
X		}
X		arts[top].artnum = atol(buf);
X
X		if (fgets(buf, 200, fp) == NULL) {
X			fprintf(stderr, "three\n");
X			goto corrupt_index;
X		}
X
X		my_strncpy(arts[top].subject, buf, MAX_SUBJ-1);
X			
X		if (fgets(buf, 200, fp) == NULL) {
X			fprintf(stderr, "four\n");
X			goto corrupt_index;
X		}
X		my_strncpy(arts[top].from, buf, MAX_FROM-1);
X
X		if (fgets(buf, 200, fp) == NULL) {
X			fprintf(stderr, "five\n");
X			goto corrupt_index;
X		}
X		arts[top].hash = atol(buf);
X
X		if (fgets(buf, 200, fp) == NULL) {
X			fprintf(stderr, "six\n");
X			goto corrupt_index;
X		}
X
X		j = atol(buf);
X#if 0
X		if (j < 0 || j > 100) {
X#if 0
X			goto corrupt_index;
X#else
X			arts[top].nore = eat_re(arts[top].subject);
X#endif
X		} else
X			arts[top].nore = arts[top].subject + j;
X#else
X		arts[top].nore = eat_re(arts[top].subject);
X#endif
X
X		top++;
X	}
X
X	fclose(fp);
X	return;
X
Xcorrupt_index:
X	fprintf(stderr, "index file %s corrupt\n", index_file);
X	fprintf(stderr, "top = %d\n", top);
X	exit(1);
X	unlink(index_file);
X	top = 0;
X}
X
X
X/*
X *  Look in the local $HOME/.tindex (or wherever) directory for the
X *  index file for the given group.  Hashing the group name gets
X *  a number.  See if that #.1 file exists; if so, read first line.
X *  Group we want?  If no, try #.2.  Repeat until no such file or
X *  we find an existing file that matches our group.
X */
Xvoid
Xfind_local_index(group)
Xchar *group;
X{
X	unsigned long h;
X	static char buf[200];
X	int i;
X	char *p;
X	FILE *fp;
X
X	{
X		char *t = group;
X
X		h = *t++;
X		while (*t)
X			h = (h << 1) + (*t++ & 0xff);
X	}
X
X	i = 1;
X	while (1) {
X		sprintf(index_file, "%s/%lu.%d", indexdir, h, i);
X		fp = fopen(index_file, "r");
X		if (fp == NULL)
X			return;
X
X		if (fgets(buf, 200, fp) == NULL) {
X			fclose(fp);
X			return;
X		}
X		fclose(fp);
X
X		for (p = buf; *p && *p != '\n'; p++) ;
X		*p = '\0';
X
X		if (strcmp(buf, group) == 0)
X			return;
X
X		i++;
X	}
X}
X
X
X/*
X *  Run the index file updater only for the groups we've loaded.
X */
X
Xdo_update() {
X	int i;
X	char group_path[200];
X	char *p;
X#ifdef	OSK
X	int len = 0;
X#endif	/* OSK */
X	time_t tim;
X	struct tm *tt;
X
X	complete_count = 0;
X	complete_expire = 0;
X
X	for (i = 0; i < local_top; i++) {
X		strcpy(group_path, active[my_group[i]].name);
X		for (p = group_path; *p; p++)
X#ifndef	OSK
X			if (*p == '.')
X				*p = '/';
X#else	/* OSK */
X		{
X			if (*p == '.') {
X				*p = '/';
X				len = 0;
X			} else if ((! isalnum (*p)) && (! index ("$_", *p)))
X				*p = '_';
X			if (++len > 26)
X				while (*(p + 1) && (*(p + 1) != '.'))
X					++p;
X		}
X#endif	/* OSK */
X
X		index_group(active[my_group[i]].name, group_path);
X	}
X
X	time (&tim);
X	tt = localtime (&tim);
X	printf ("[%02d.%02d.%04d %2d:%2d] Collected %d articles",
X		tt -> tm_mday,
X		tt -> tm_mon + 1,
X		tt -> tm_year + 1900,
X		tt -> tm_hour,
X		tt -> tm_min,
X		complete_count);
X
X	if (complete_expire)
X		printf (" (%d articles expired)", complete_expire);
X	putchar ('\n');
X}
__END__OF__THIS__FILE__
if test -f 'curses.c' ; then
  echo 'File curses.c already exists, overwriting it'
  del 'curses.c'
fi
echo Extracting \"'curses.c'\"
sed "s/^X//" >'curses.c' <<'__END__OF__THIS__FILE__'
X
X/*
X *  This is a screen management library borrowed with permission from the
X *  Elm mail system (a great mailer--I highly recommend it!).
X *
X *  I've hacked this library to only provide what Tass needs.
X *
X *  Original copyright follows:
X */
X
X/*******************************************************************************
X *  The Elm Mail System  -  $Revision: 2.1 $   $State: Exp $
X *
X * 			Copyright (c) 1986 Dave Taylor
X ******************************************************************************/
X
X#include <stdio.h>
X#ifndef	OSK
X#include <curses.h>
X#include <sys/ioctl.h>
X#else	/* OSK */
X#include <termcap.h>
X#include <sgstat.h>
Xchar	PC_, *UP, *BC;
Xshort	ospeed;
X#endif	/* OSK */
X#include "tass.h"
X
X#define		BACKSPACE	'\b'
X#define		VERY_LONG_STRING	2500
X
Xint LINES=23;
Xint COLS=80;
X
Xint inverse_okay = TRUE;
X
X/*
X#ifdef BSD
X#  ifndef BSD4_1
X#    include <sgtty.h>
X#  else
X#    include <termio.h>
X#  endif
X# else
X#  include <termio.h>
X#endif
X*/
X
X#include <ctype.h>
X
X/*
X#ifdef BSD
X#undef tolower
X#endif
X*/
X
X#define TTYIN	0
X
X#ifdef SHORTNAMES
X# define _clearinverse	_clrinv
X# define _cleartoeoln	_clrtoeoln
X# define _cleartoeos	_clr2eos
X#endif
X
X#ifndef BSD
X#ifndef	OSK
Xstruct termio _raw_tty, 
X              _original_tty;
X#else	/* OSK */
Xstruct sgbuf	_raw_tty, 
X		_original_tty;
X#endif	/* OSK */
X#else
X#define TCGETA	TIOCGETP
X#define TCSETAW	TIOCSETP
X
Xstruct sgttyb _raw_tty,
X	      _original_tty;
X#endif
X
Xstatic int _inraw = 0;                  /* are we IN rawmode?    */
X
X#define DEFAULT_LINES_ON_TERMINAL	24
X#define DEFAULT_COLUMNS_ON_TERMINAL	80
X
Xstatic int _memory_locked = 0;		/* are we IN memlock??   */
X
Xstatic int _intransmit;			/* are we transmitting keys? */
X
Xstatic
Xchar *_clearscreen, *_moveto, *_cleartoeoln, *_cleartoeos,
X	*_setinverse, *_clearinverse, *_setunderline, *_clearunderline;
X
X# define	K_UP		0
X# define	K_DOWN		1
X# define	K_LEFT		2
X# define	K_RIGHT		3
X# define	K_PUP		4
X# define	K_PDOWN		5
X
X# define	K_MAX		6
X
Xstatic
Xchar *_spckey[K_MAX];
Xstatic
Xchar *_spckeytc[K_MAX] = {
X	"ku", "kd", "kl", "kr", "kP", "kN"
X};
X
Xstatic
Xint _lines,_columns;
X
Xstatic char _terminal[1024];              /* Storage for terminal entry */
Xstatic char _capabilities[1024];           /* String for cursor motion */
X
Xstatic char *ptr = _capabilities;	/* for buffering         */
X
Xint    outchar();			/* char output for tputs */
Xchar  *tgetstr(),     		       /* Get termcap capability */
X      *tgoto();				/* and the goto stuff    */
X
XInitScreen()
X{
Xint  tgetent(),      /* get termcap entry */
X     err;
Xchar termname[40];
Xchar *strcpy(), *getenv();
Xint i;
X	
X	if (getenv("TERM") == NULL) {
X		fprintf(stderr,
X		  "TERM variable not set; Tass requires screen capabilities\n");
X		return(FALSE);
X	}
X	if (strcpy(termname, getenv("TERM")) == NULL) {
X		fprintf(stderr,"Can't get TERM variable\n");
X		return(FALSE);
X	}
X	if ((err = tgetent(_terminal, termname)) != 1) {
X		fprintf(stderr,"Can't get entry for TERM\n");
X		return(FALSE);
X	}
X
X	/* load in all those pesky values */
X	_clearscreen       = tgetstr("cl", &ptr);
X	_moveto            = tgetstr("cm", &ptr);
X	_cleartoeoln       = tgetstr("ce", &ptr);
X	_cleartoeos        = tgetstr("cd", &ptr);
X	_lines	      	   = tgetnum("li");
X	_columns	   = tgetnum("co");
X	_setinverse        = tgetstr("so", &ptr);
X	_clearinverse      = tgetstr("se", &ptr);
X	_setunderline      = tgetstr("us", &ptr);
X	_clearunderline    = tgetstr("ue", &ptr);
X
X	if (!_clearscreen) {
X		fprintf(stderr,
X			"Terminal must have clearscreen (cl) capability\n");
X		return(FALSE);
X	}
X	if (!_moveto) {
X		fprintf(stderr,
X			"Terminal must have cursor motion (cm)\n");
X		return(FALSE);
X	}
X	if (!_cleartoeoln) {
X		fprintf(stderr,
X			"Terminal must have clear to end-of-line (ce)\n");
X		return(FALSE);
X	}
X	if (!_cleartoeos) {
X		fprintf(stderr,
X			"Terminal must have clear to end-of-screen (cd)\n");
X		return(FALSE);
X	}
X	if (_lines == -1)
X		_lines = DEFAULT_LINES_ON_TERMINAL;
X	if (_columns == -1)
X		_columns = DEFAULT_COLUMNS_ON_TERMINAL;
X
X	for (i = 0; i < K_MAX; ++i)
X		_spckey[i] = tgetstr (_spckeytc[i], &ptr);
X
X	return(TRUE);
X}
X
XScreenSize(lines, columns)
Xint *lines, *columns;
X{
X	/** returns the number of lines and columns on the display. **/
X
X	if (_lines == 0) _lines = DEFAULT_LINES_ON_TERMINAL;
X	if (_columns == 0) _columns = DEFAULT_COLUMNS_ON_TERMINAL;
X
X	*lines = _lines - 1;		/* assume index from zero*/
X	*columns = _columns;		/* assume index from one */
X}
X
XClearScreen()
X{
X	/* clear the screen: returns -1 if not capable */
X
X	tputs(_clearscreen, 1, outchar);
X	fflush(stdout);      /* clear the output buffer */
X}
X
XMoveCursor(row, col)
Xint row, col;
X{
X	/** move cursor to the specified row column on the screen.
X            0,0 is the top left! **/
X
X	char *stuff, *tgoto();
X
X	stuff = tgoto(_moveto, col, row);
X	tputs(stuff, 1, outchar);
X	fflush(stdout);
X}
X
XCleartoEOLN()
X{
X	/** clear to end of line **/
X
X	tputs(_cleartoeoln, 1, outchar);
X	fflush(stdout);  /* clear the output buffer */
X}
X
XCleartoEOS()
X{
X	/** clear to end of screen **/
X
X	tputs(_cleartoeos, 1, outchar);
X	fflush(stdout);  /* clear the output buffer */
X}
X
XStartInverse()
X{
X	/** set inverse video mode **/
X
X	if (_setinverse && inverse_okay)
X		tputs(_setinverse, 1, outchar);
X/*	fflush(stdout);	*/
X}
X
X
XEndInverse()
X{
X	/** compliment of startinverse **/
X
X	if (_clearinverse && inverse_okay)
X		tputs(_clearinverse, 1, outchar);
X/*	fflush(stdout);	*/
X}
X
X#if 0
XStartUnderline()
X{
X	/** start underline mode **/
X
X	if (!_setunderline)
X		return(-1);
X
X	tputs(_setunderline, 1, outchar);
X	fflush(stdout);
X	return(0);
X}
X
X
XEndUnderline()
X{
X	/** the compliment of start underline mode **/
X
X	if (!_clearunderline)
X		return(-1);
X
X	tputs(_clearunderline, 1, outchar);
X	fflush(stdout);
X	return(0);
X}
X#endif
X
XRawState()
X{
X	/** returns either 1 or 0, for ON or OFF **/
X
X	return( _inraw );
X}
X
XRaw(state)
Xint state;
X{
X	/** state is either TRUE or FALSE, as indicated by call **/
X
X	if (state == FALSE && _inraw) {
X#ifndef	OSK
X	  (void) ioctl(TTYIN, TCSETAW, &_original_tty);
X#else	/* OSK */
X	  (void) _ss_opt (TTYIN, &_original_tty);
X#endif	/* OSK */
X	  _inraw = 0;
X	}
X	else if (state == TRUE && ! _inraw) {
X
X#ifndef	OSK
X	  (void) ioctl(TTYIN, TCGETA, &_original_tty);	/** current setting **/
X
X	  (void) ioctl(TTYIN, TCGETA, &_raw_tty);    /** again! **/
X#ifdef BSD
X	  _raw_tty.sg_flags &= ~(ECHO | CRMOD);	/* echo off */
X	  _raw_tty.sg_flags |= CBREAK;	/* raw on    */
X#else
X	  _raw_tty.c_lflag &= ~(ICANON | ECHO);	/* noecho raw mode        */
X
X	  _raw_tty.c_cc[VMIN] = '\01';	/* minimum # of chars to queue    */
X	  _raw_tty.c_cc[VTIME] = '\0';	/* minimum time to wait for input */
X
X#endif
X	  (void) ioctl(TTYIN, TCSETAW, &_raw_tty);
X#else	/* OSK */
X	  (void) _gs_opt (TTYIN, &_original_tty);
X	  _raw_tty = _original_tty;
X	  _raw_tty.sg_echo = 0;
X	  _raw_tty.sg_pause = 0;
X	  _raw_tty.sg_alf = 0;
X	  _raw_tty.sg_psch = 0;
X	  _raw_tty.sg_eofch = 0;
X	  
X	  (void) _ss_opt (TTYIN, &_raw_tty);
X#endif	/* OSK */
X
X	  _inraw = 1;
X	}
X}
X
Xint
XReadCh()
X{
X	/** read a character with Raw mode set! **/
X
X	register int result;
X	char ch;
X	register int i, j, n;
X	register char *spc;
X
X	spc = NULL;
X	do {
X		if (spc) {
X#ifdef	OSK
X			tsleep (4);
X			if (_gs_rdy (0) < 1)
X				tsleep (50 | (1 << 31));
X			if (_gs_rdy (0) < 1)
X#else	/* OSK */
X#ifdef	FIONREAD
X#define	CH_READ		FIONREAD
X#else	/* FIONREAD */
X#ifdef	TCRDCHK
X#define	CH_READ		TCRDCHK
X#else	/* TCRDCHK */
X	Panic!
X#endif	/* TCRDCHK */
X#endif	/* FIONREAD */
X			int tmp;
X
X			ioctl (0, CH_READ, &tmp);
X			if (tmp < 1)
X				nap (200);
X			ioctl (0, CH_READ, &tmp);
X			if (tmp < 1)
X#endif	/* OSK */
X				spc = NULL;
X		}
X		if ((result = read(0, &ch, 1)) != 1)
X			break;
X		if (!spc) {
X			for (i = 0; i < K_MAX; ++i)
X				if (_spckey[i] && (_spckey[i][0] == ch)) {
X					spc = _spckey[i];
X					n = 1;
X					break;
X				}
X		} else if (ch != spc[n]) {
X			for (j = 0; j < K_MAX; ++j)
X				if (!_spckey[j])
X					continue;
X				else if (spc[0]) {
X					if ((!strncmp (spc, _spckey[j], n - 1)) && (_spckey[j][n] == ch))
X						break;
X				} else if (spc[0] == _spckey[j][0]) {
X					if (n > 1) {
X						if ((!strncmp (spc + 1, _spckey[j] + 1, n - 2)) && (_spckey[j][n] == ch))
X							break;
X					} else if (_spckey[j][n] == ch)
X						break;
X				}
X			if (j < K_MAX) {
X				spc = _spckey[j];
X				i = j;
X				++n;
X			} else {
X				spc = NULL;
X				result = -1;
X			}
X		} else
X			++n;
X		if (spc && (!spc[n])) {
X			spc = NULL;
X			switch (i) {
X				case K_UP:
X					ch = ctrl('P');
X					break;
X				case K_DOWN:
X					ch = ctrl('N');
X					break;
X				case K_LEFT:
X					ch = ctrl('B');
X					break;
X				case K_RIGHT:
X					ch = ctrl('F');
X					break;
X				case K_PUP:
X					ch = ctrl('Z');
X					break;
X				case K_PDOWN:
X					ch = ctrl('V');
X					break;
X			}
X		}
X	} while (spc || (result < 0));
X        return((result <= 0 ) ? EOF : ch & 0x7F);
X}
X
X
Xoutchar(c)
Xchar c;
X{
X	/** output the given character.  From tputs... **/
X	/** Note: this CANNOT be a macro!              **/
X
X	putc(c, stdout);
X}
X
X#ifdef	NO_NAP
Xnap (n)
Xint n;
X{
X	int	t;
X
X	for (t = 100 * n; t > 0; --t)
X		;
X}
X#endif	/* NO_NAP */
__END__OF__THIS__FILE__
if test -f 'group.c' ; then
  echo 'File group.c already exists, overwriting it'
  del 'group.c'
fi
echo Extracting \"'group.c'\"
sed "s/^X//" >'group.c' <<'__END__OF__THIS__FILE__'
X
X
X#include	<stdio.h>
X#include	<signal.h>
X#ifdef		REGEXP
X#include	<regexp.h>
X#endif		/* REGEXP */
X#ifdef	OSK
X#include	<ctype.h>
X#endif	/* OSK */
X#include	"tass.h"
X
X#ifdef	GET_DIST
Xextern char	*index ();
X#endif	/* GET_DIST */
X
Xint index_point;
Xint first_subj_on_screen;
Xint last_subj_on_screen;
Xchar subject_search_string[LEN+1];	/* last search pattern */
Xextern int cur_groupnum;
Xextern int last_resp;		/* page.c */
Xextern int this_resp;		/* page.c */
Xextern int space_mode;		/* select.c */
Xextern char *cvers;
X
Xchar *glob_group;
X
X
X#ifdef SIGTSTP
Xvoid
Xgroup_susp(i)
Xint i;
X{
X
X	Raw(FALSE);
X	putchar('\n');
X	signal(SIGTSTP, SIG_DFL);
X	kill(0, SIGTSTP);
X
X	signal(SIGTSTP, group_susp);
X	Raw(TRUE);
X	mail_setup();
X	show_group_page(glob_group, TRUE);
X}
X#endif
X
X
Xgroup_page(group)
Xchar *group;
X{
X	char ch;
X	int i, n;
X	char group_path[200];
X	char *p;
X	char buf[200];
X
X	glob_group = group;
X
X	strcpy(group_path, group);		/* turn comp.unix.amiga into */
X	for (p = group_path; *p; p++)		/* comp/unix/amiga */
X		if (*p == '.')
X			*p = '/';
X#ifdef	OSK
X		else if ((!isalnum (*p)) || ((unsigned char) *p >= 0x80))
X			*p = '_';
X#endif	/* OSK */
X
X	last_resp = -1;
X	this_resp = -1;
X	index_group(group, group_path);		/* update index file */
X	read_newsrc_line(group);		/* get sequencer information */
X
X	if (space_mode) {
X		for (i = 0; i < top_base; i++)
X			if (new_responses(i))
X				break;
X		if (i < top_base)
X			index_point = i;
X		else
X			index_point = top_base - 1;
X	} else
X		index_point = top_base - 1;
X
X	show_group_page(group, TRUE);
X
X	while (1) {
X		if ((n = ReadCh()) == EOF)
X			longjmp (jmp_buffer, 1);
X		ch = (char) n;
X
X		if (ch > '0' && ch <= '9') {	/* 0 goes to basenote */
X			prompt_subject_num(ch, group);
X		} else switch (ch) {
X			case 'I':	/* toggle inverse video */
X				inverse_okay = !inverse_okay;
X				if (inverse_okay)
X					info_message("Inverse video enabled");
X				else
X					info_message("Inverse video disabled");
X				break;
X
X			case 's':	/* subscribe to this group */
X				subscribe(group, ':', my_group[cur_groupnum],
X									TRUE);
X				sprintf(buf, "subscribed to %s", group);
X				info_message(buf);
X				break;
X
X			case 'u':	/* unsubscribe to this group */
X				subscribe(group, '!', my_group[cur_groupnum],
X									TRUE);
X				sprintf(buf, "unsubscribed to %s", group);
X				info_message(buf);
X				break;
X
X			case 'g':	/* choose a new group by name */
X				n = choose_new_group();
X				if (n >= 0 && n != cur_groupnum) {
X					fix_new_highest(cur_groupnum);
X					cur_groupnum = n;
X					index_point = -3;
X					goto group_done;
X				}
X				break;
X
X			case 'c':	/* catchup--mark all articles as read */
X			    if (prompt_yn("Mark everything as read? (y/n): ")) {
X				for (n = 0; n < top; n++)
X					arts[n].unread = 0;
X				show_group_page(group, FALSE);
X				info_message("All articles marked as read");
X			    }
X			    break;
X
X#ifdef	USE_ARROW
X			case 27:	/* common arrow keys */
X				ch = ReadCh();
X				if (ch == '[' || ch == 'O')
X					ch = ReadCh();
X				switch (ch) {
X				case 'A':
X				case 'D':
X				case 'i':
X					goto group_up;
X
X				case 'B':
X				case 'I':
X				case 'C':
X					goto group_down;
X				}
X				break;
X#endif	/* USE_ARROW */
X
X			case 'n':	/* next group */
X				clear_message();
X				if (cur_groupnum + 1 >= local_top)
X					info_message("No more groups");
X				else {
X					fix_new_highest(cur_groupnum);
X					cur_groupnum++;
X					index_point = -3;
X					space_mode = FALSE;
X					goto group_done;
X				}
X				break;
X
X			case 'p':	/* previous group */
X				clear_message();
X				if (cur_groupnum <= 0)
X					info_message("No previous group");
X				else {
X					fix_new_highest(cur_groupnum);
X					cur_groupnum--;
X					index_point = -3;
X					space_mode = FALSE;
X					goto group_done;
X				}
X				break;
X
X			case ' ':
X				if (top_base <= 0)
X					info_message("*** No Articles ***");
X				else if (last_subj_on_screen == top_base)
X					info_message("*** End of Articles ***");
X				else
X					clear_message();
X				break;
X
X			case '\t':
X				fix_new_highest(cur_groupnum);
X				space_mode = TRUE;
X
X				if (index_point < 0
X				|| (n=next_unread((int) base[index_point]))<0) {
X					for (i = cur_groupnum+1;
X							i < local_top; i++)
X						if (unread[i] > 0)
X							break;
X					if (i >= local_top)
X						goto group_done;
X
X					cur_groupnum = i;
X					index_point = -3;
X					goto group_done;
X				}
X				index_point = show_page(n, group, group_path);
X				if (index_point < 0)
X					goto group_done;
X				show_group_page(group, TRUE);
X				break;
X	
X
X			case 'N':	/* go to next unread article */
X				if (index_point < 0) {
X					info_message("No next unread article");
X					break;
X				}
X
X				n = next_unread( (int) base[index_point]);
X				if (n == -1)
X					info_message("No next unread article");
X				else {
X					index_point =
X						show_page(n, group, group_path, TRUE);
X					if (index_point < 0) {
X						fix_new_highest(cur_groupnum);
X						space_mode = FALSE;
X						goto group_done;
X					}
X					show_group_page(group, TRUE);
X				}
X				break;
X
X			case 'P':	/* go to previous unread article */
X				if (index_point < 0) {
X				    info_message("No previous unread article");
X				    break;
X				}
X
X				n = prev_response( (int) base[index_point]);
X				n = prev_unread(n);
X				if (n == -1)
X				    info_message("No previous unread article");
X				else {
X					index_point =
X						show_page(n, group, group_path, TRUE);
X					if (index_point < 0) {
X						fix_new_highest(cur_groupnum);
X						space_mode = FALSE;
X						goto group_done;
X					}
X					show_group_page(group, TRUE);
X				}
X				break;
X
X			case 'w':	/* post a basenote */
X				post_base(group);
X				update_newsrc(group, my_group[cur_groupnum]);
X				index_group(group, group_path);
X				read_newsrc_line(group);
X				index_point = top_base - 1;
X				show_group_page(group, TRUE);
X				break;
X
X			case 't':	/* return to group selection page */
X			case ctrl('B'):	/* cursor key */
X				fix_new_highest(cur_groupnum);
X				goto group_done;
X
X#ifndef	OSK
X			case '\r':
X#else	/* OSK */
X			case '\l':
X#endif	/* OSK */
X			case '\n':	/* read current basenote */
X			case ctrl('F'):	/* cursor key */
X				if (index_point < 0) {
X					info_message("*** No Articles ***");
X					break;
X				}
X				index_point = show_page((int) base[index_point],
X							group, group_path, TRUE);
X				if (index_point < 0) {
X					fix_new_highest(cur_groupnum);
X					space_mode = FALSE;
X					goto group_done;
X				}
X				show_group_page(group, TRUE);
X				break;
X
X			case ctrl('D'):		/* page down */
X			case ctrl('V'):		/* full page down */
X				if (!top_base || index_point == top_base - 1)
X					break;
X
X				erase_subject_arrow();
X				index_point += (ch == ctrl('V')) ? 
X						NOTESLINES :
X						NOTESLINES / 2;
X				if (index_point >= top_base)
X					index_point = top_base - 1;
X
X				if (index_point < first_subj_on_screen
X				|| index_point >= last_subj_on_screen)
X					show_group_page(group, TRUE);
X				else
X					draw_subject_arrow();
X				break;
X
X			case '-':	/* go to last viewed article */
X				if (this_resp < 0) {
X					info_message("No last message");
X					break;
X				}
X				index_point = show_page(this_resp,
X							group, group_path, TRUE);
X				if (index_point < 0) {
X					fix_new_highest(cur_groupnum);
X					space_mode = FALSE;
X					goto group_done;
X				}
X				show_group_page(group, TRUE);
X				break;
X
X			case ctrl('U'):		/* page up */
X			case ctrl('Z'):		/* full page up */
X				if (!top_base)
X					break;
X
X				erase_subject_arrow();
X				index_point -= (ch == ctrl('Z')) ?
X						NOTESLINES :
X						NOTESLINES / 2;
X				if (index_point < 0)
X					index_point = 0;
X				if (index_point < first_subj_on_screen
X				|| index_point >= last_subj_on_screen)
X					show_group_page(group, TRUE);
X				else
X					draw_subject_arrow();
X				break;
X
X			case 'v':
X				info_message(cvers);
X				break;
X
X			case '!':
X				shell_escape();
X				show_group_page(group, TRUE);
X				break;
X
X			case ctrl('N'):
X			case 'j':		/* line down */
X#ifdef	USE_ARROW
Xgroup_down:
X#endif	/* USE_ARROW */
X				if (!top_base || index_point + 1 >= top_base)
X					break;
X
X				erase_subject_arrow();
X				index_point++;
X				if (index_point >= last_subj_on_screen)
X					show_group_page(group, FALSE);
X				else
X					draw_subject_arrow();
X				break;
X
X			case ctrl('P'):
X			case 'k':		/* line up */
X#ifdef	USE_ARROW
Xgroup_up:
X#endif	/* USE_ARROW */
X				if (!top_base || !index_point)
X					break;
X
X				erase_subject_arrow();
X				index_point--;
X				if (index_point + 1 <= first_subj_on_screen)
X					show_group_page(group, FALSE);
X				else
X					draw_subject_arrow();
X				break;
X
X			case ctrl('R'):
X			case ctrl('L'):
X			case ctrl('W'):
X			case 'i':		/* return to index */
X					show_group_page(group, FALSE);
X					break;
X
X			case '/':		/* forward search */
X					search_subject(TRUE, group);
X					break;
X
X			case '?':		/* backward search */
X					search_subject(FALSE, group);
X					break;
X
X#ifndef	OSK
X			case 'q':		/* quit */
X					index_point = -2;
X					fix_new_highest(cur_groupnum);
X					space_mode = FALSE;
X					goto group_done;
X#endif	/* OSK */
X
X			case 'h':
X				tass_group_help();
X				show_group_page(group, TRUE);
X				break;
X
X			default:
X			    info_message("Bad command.  Type 'h' for help.");
X		}
X	}
X
Xgroup_done:
X	update_newsrc(group, my_group[cur_groupnum]);
X
X	if (index_point == -2)
X		tass_done(0);
X}
X
X
X/*
X *  Correct highest[] for the group selection page display since
X *  new articles may have been read or marked unread
X */
X
Xfix_new_highest(groupnum)
Xint groupnum;
X{
X	int i;
X	int sum = 0;
X
X	for (i = 0; i < top; i++)
X		if (arts[i].unread)
X			sum++;
X
X	unread[groupnum] = sum;
X}
X
X
Xshow_mail () {
X	if (mail_check()) {			/* you have mail message in */
X		MoveCursor(0, COLS - 14);		/* upper right */
X		printf("you have mail");
X		fflush (stdout);
X	}
X}
X
Xshow_group_page(group, clr)
Xchar *group;
Xint clr;
X{
X	int i;
X	int n;
X	char resps[10];
X	char new_resps;
X	int respnum;
X
X#ifdef SIGTSTP
X	signal(SIGTSTP, group_susp);
X#endif
X
X	if (clr) {
X		ClearScreen();
X		printf("%s\r\012", nice_time());	/* time in upper left */
X		center_line(1, group);
X	}
X
X	show_mail ();
X
X	MoveCursor(INDEX_TOP, 0);
X
X	first_subj_on_screen = (index_point / NOTESLINES) * NOTESLINES;
X	if (first_subj_on_screen < 0)
X		first_subj_on_screen = 0;
X
X	last_subj_on_screen = first_subj_on_screen + NOTESLINES;
X	if (last_subj_on_screen >= top_base) {
X		last_subj_on_screen = top_base;
X		first_subj_on_screen = top_base - NOTESLINES;
X
X		if (first_subj_on_screen < 0)
X			first_subj_on_screen = 0;
X	}
X
X	for (i = first_subj_on_screen; i < last_subj_on_screen; i++) {
X		if (new_responses(i))
X			new_resps = '+';
X		else
X			new_resps = ' ';
X
X		n = nresp(i);
X		if (n)
X			sprintf(resps, "%4d", n);
X		else
X			strcpy(resps, "    ");
X
X		respnum = base[i];
X
X		MoveCursor(INDEX_TOP + i - first_subj_on_screen, 3);
X		printf("%4d %c %s", i + 1, new_resps, arts[respnum].subject);
X		CleartoEOLN ();
X		MoveCursor(INDEX_TOP + i - first_subj_on_screen, COLS - 35);
X		printf ("%s %-.30s", resps, arts[respnum].from);
X		MoveCursor(INDEX_TOP + i - first_subj_on_screen, COLS - 1);
X		CleartoEOLN ();
X	}
X
X	if (top_base <= 0)
X		info_message("*** No Articles ***");
X	else if (last_subj_on_screen == top_base)
X		info_message("*** End of Articles ***");
X
X	if (top_base > 0)
X		draw_subject_arrow();
X}
X
Xdraw_subject_arrow() {
X
X	draw_arrow(INDEX_TOP + (index_point-first_subj_on_screen) );
X}
X
Xerase_subject_arrow() {
X
X	erase_arrow(INDEX_TOP + (index_point-first_subj_on_screen) );
X}
X
X
Xprompt_subject_num(ch, group)
Xchar ch;
Xchar *group;
X{
Xint num;
X
X
X	clear_message();
X
X	if ((num = parse_num(ch, "Read article> ")) == -1) {
X		clear_message();
X		return FALSE;
X	}
X	num--;		/* index from 0 (internal) vs. 1 (user) */
X
X	if (num >= top_base)
X		num = top_base - 1;
X
X	if (num >= first_subj_on_screen
X	&&  num < last_subj_on_screen) {
X		erase_subject_arrow();
X		index_point = num;
X		draw_subject_arrow();
X	} else {
X		index_point = num;
X		show_group_page(group, TRUE);
X	}
X}
X
Xvoid
Xsearch_subject(forward, group)
Xint forward;
Xchar *group;
X{
X	char buf[LEN+1];
X	int i;
X#ifndef	REGEXP
X	extern char *regcmp();
X	extern char *regex();
X	char *re;
X#else	/* REGEXP */
X	regexp *re;
X#endif	/* REGEXP */
X	char *prompt;
X
X	clear_message();
X
X	if (forward)
X		prompt = "/";
X	else
X		prompt = "?";
X
X	if (!parse_string(prompt, buf))
X		return;
X
X	if (strlen(buf))
X		strcpy(subject_search_string, buf);
X	else if (!strlen(subject_search_string)) {
X		info_message("No search pattern");
X		return;
X	}
X
X	i = index_point;
X
X	glob_name(subject_search_string, buf);
X
X#ifndef	REGEXP
X	if ((re = regcmp(buf, NULL)) == NULL) {
X#else	/* REGEXP */
X	if ((re = regcomp (buf)) == NULL) {
X#endif	/* REGEXP */
X		info_message("Bad search pattern");
X		return;
X	}
X
X	do {
X		if (forward)
X			i++;
X		else
X			i--;
X
X		if (i >= top_base)
X			i = 0;
X		if (i < 0)
X			i = top_base - 1;
X
X#ifndef	REGEXP
X		if (regex(re, arts[ base[i] ].subject) != NULL) {
X#else	/* REGEXP */
X		if (regexec (re, arts[ base[i] ].subject) != NULL) {
X#endif	/* REGEXP */
X			if (i >= first_subj_on_screen
X			&&  i < last_subj_on_screen) {
X				erase_subject_arrow();
X				index_point = i;
X				draw_subject_arrow();
X			} else {
X				index_point = i;
X				show_group_page(group, TRUE);
X			}
X			return;
X		}
X	} while (i != index_point);
X
X	info_message("No match");
X}
X
X
X/*
X *  Post an original article (not a followup)
X */
X
Xpost_base(group)
Xchar *group;
X{
X	FILE *fp;
X	char nam[100];
X	char ch;
X	char subj[LEN+1];
X	char buf[200];
X	int line;
X#ifdef	GET_DIST
X	char *ptr;
X#endif	/* GET_DIST */
X
X	if (!parse_string("Subject: ", subj))
X		return(FALSE);
X	if (subj[0] == '\0')
X		return(FALSE);
X
X#ifdef	USE_UID
X	setuid(real_uid);
X	setgid(real_gid);
X#endif	/* USE_UID */
X
X	sprintf(nam, "%s/.article", homedir);
X	umask (omask);
X	if ((fp = fopen(nam, "w")) == NULL) {
X		umask (0);
X		fprintf(stderr, "can't open %s: ", nam);
X		perror("");
X		return(FALSE);
X	}
X#ifndef	OSK
X	chmod(nam, 0600);
X#else	/* OSK */
X	chmod(nam, 013);
X#endif	/* OSK */
X
X	fprintf(fp, "Subject: %s\n", subj);
X	fprintf(fp, "Newsgroups: %s\n", group);
X#ifndef	GET_DIST
X/*	fprintf(fp, "Distribution: \n");	*/
X	line = 2;
X#else	/* GET_DIST */
X	strcpy (buf, group);
X	if (ptr = index (buf, '.'))
X		*ptr = '\0';
X	fprintf(fp, "Distribution: %s\n", buf);
X	line = 3;
X#endif	/* GET_DIST */
X	if (*my_org) {
X		fprintf(fp, "Organization: %s\n", my_org);
X		++line;
X	}
X	fprintf(fp, "\n");
X	++line;
X
X#ifdef	APPEND_SIG
X	add_signature (fp);
X#endif	/* APPEND_SIG */
X
X	fclose(fp);
X	umask (0);
X
X	ch = 'e';
X	while (1) {
X		switch (ch) {
X		case 'e':
X			invoke_editor(nam, line);
X			break;
X
X		case 'a':
X			return FALSE;
X
X		case 'p':
X			printf("\rPosting...  ");
X			CleartoEOLN ();
X#ifndef	OSK
X			sprintf(buf, "%s/inews -h < %s", LIBDIR, nam);
X#else	/* OSK */
X			sprintf(buf, "inews -h <%s", nam);
X#endif	/* OSK */
X			if (invoke_cmd(buf)) {
X				printf("article posted ");
X				fflush(stdout);
X				goto post_base_done;
X			} else {
X				printf("article rejected ");
X				fflush(stdout);
X				break;
X			}
X		}
X
X		do {
X			MoveCursor(LINES, 0);
X			fputs("abort, edit, post: ", stdout);
X			fflush(stdout);
X			ch = ReadCh();
X		} while (ch != 'a' && ch != 'e' && ch != 'p');
X	}
X
Xpost_base_done:
X#ifdef	USE_UID
X	setuid(tass_uid);
X	setgid(tass_gid);
X#endif	/* USE_UID */
X
X	continue_prompt();
X
X	return(TRUE);
X}
X
X
X/*
X *  Return the number of unread articles there are within a thread
X */
X
Xnew_responses(thread)
Xint thread;
X{
X	int i;
X	int sum = 0;
X
X	for (i = base[thread]; i >= 0; i = arts[i].thread)
X		if (arts[i].unread)
X			sum++;
X
X	return sum;
X}
X
Xvoid
Xtass_group_help() {
X	char ch;
X
Xgroup_help_start:
X
X	ClearScreen();
X	center_line(0, TASS_HEADER);
X	center_line(1, "Index Page Commands (page 1 of 2)");
X
X	MoveCursor(3, 0);
X
X	printf("4        Select article 4\r\012");
X	printf("^D, ^V   half / full Page down\r\012");
X	printf("^U, ^Z   half / full  Page up\r\012");
X	printf("<CR>     Read current article\r\012");
X	printf("<TAB>    View next unread article or group\r\012");
X	printf("c        Mark all articles as read\r\012");
X	printf("g        Choose a new group by name\r\012");
X	printf("j        Down a line\r\012");
X	printf("k        Up a line\r\012");
X	printf("n        Go to next group\r\012");
X	printf("N        Go to next unread article\r\012");
X	printf("p        Go to previous group\r\012");
X	printf("P        Go to previous unread article\r\012");
X	printf("q        Quit\r\012");
X
X	center_line(LINES, "-- hit space for more commands --");
X	ch = ReadCh();
X	if (ch != ' ')
X		return;
X
X	ClearScreen();
X	center_line(0, TASS_HEADER);
X	center_line(1, "Index Page Commands (page 2 of 2)");
X
X	MoveCursor(3, 0);
X
X	printf("s        Subscribe to this group\r\012");
X	printf("t        Return to group selection index\r\012");
X	printf("u        Unsubscribe to this group\r\012");
X	printf("w        Post an article\r\012");
X	printf("/        Search forward for subject\r\012");
X	printf("?        Search backward for subject\r\012");
X	printf("-        Show last message\r\012");
X
X	center_line(LINES, "-- hit any key --");
X	ch = ReadCh();
X	if (ch == 'b')
X		goto group_help_start;
X}
__END__OF__THIS__FILE__
exit 0
: end of shell archive

-- 
Frank Kaefer # fkk@stasys.sta.sub.org # Starnberg, Germany
Xref: sparky de.comp.sources.os9:4 comp.os.os9:1546