Subject:  v22i040:  NN Newsreader, release 6.4, Part05/21
Newsgroups: comp.sources.unix
Approved: rsalz@uunet.UU.NET
X-Checksum-Snefru: 6cc7a4e0 72b69816 29afbd42 7cfb04a3

Submitted-by: "Kim F. Storm" <storm@texas.dk>
Posting-number: Volume 22, Issue 40
Archive-name: nn6.4/part05

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  man/nngrab.1 newsrc.c sequence.c
# Wrapped by storm@texas.dk on Sun May  6 18:19:25 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 5 (of 22)."'
if test -f 'man/nngrab.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'man/nngrab.1'\"
else
  echo shar: Extracting \"'man/nngrab.1'\" \(1616 characters\)
  sed "s/^X//" >'man/nngrab.1' <<'END_OF_FILE'
X.TH NNGRAB 1 "Release 6.4"
X.UC 4
X.SH NAME
Xnngrab \- news retrieval by keyword (nn)
X.SH SYNOPSIS
X.B nngrab
X.I keyword
X.SH DESCRIPTION
X.I nngrab
Xinvokes \fInn\fP
Xon all USENET articles whose subject (or keyword) field(s)
Xcontain an instance of \fIkeyword\fP.  \fInngrab\fP is a fast
Xequivalent for:
X.sp 0.5v
X	nn -mxX -s/\fIkeyword\fP all
X.LP
XFor example,
X.sp 0.5v
X	nngrab tesla
X.sp 0.5v
Xwill retrieve items concerning Nikola Tesla.
X.LP
XKeyword case is ignored, and the \fIkeyword\fP can be a regular
Xexpressions (escaped to avoid conflicts with the shell).  For example,
X.sp 0.5v
X	nngrab "n.*tesla"
X.LP
XThe range of search includes all newsgroups on the system,
Xincluding ones which are unsubscribed.
X.SH FILES
X.DT
X.ta \w'$db/subjects'u+3m
X.\"ta 0 16
X$db/subjects	subject database
X.DT
X.SH SEE ALSO
Xnn(1), nnspew(8), egrep(1)
X.SH NOTES
X\fInngrap\fP \fIcan be\fP much faster than the equivalent command
Xshown above, if the tertiary news subject
Xdatabase generated by the \fInnspew\fP(8) daemon exists.  To enable
Xthe faster operation, \fInnspew\fP must be executed regularly by cron.
X.LP
X\fInngrab\fP uses \fIegrep\fP(1) to scan the subject database, so
Xif you are not running \fIfast\fP egrep (GNU-style) this is all for
Xnaught.
X.LP
X\fInngrab\fP will use a subject database generated by \fInnspew\fP
Xindependent of its age.  Thus, if you stop running \fInnspew\fP,
Xremember to remove the subjects file as well.
X.SH BUGS
XUnder version 6.4, search of the "Keywords:" field is not supported.
X.br
XSearch on name is not possible either.
X.SH AUTHOR
XJames A. Woods, NASA Ames Research Center
X.br
XE-mail: jaw@ames.arc.nasa.gov
END_OF_FILE
  if test 1616 -ne `wc -c <'man/nngrab.1'`; then
    echo shar: \"'man/nngrab.1'\" unpacked with wrong size!
  fi
  # end of 'man/nngrab.1'
fi
if test -f 'newsrc.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'newsrc.c'\"
else
  echo shar: Extracting \"'newsrc.c'\" \(34671 characters\)
  sed "s/^X//" >'newsrc.c' <<'END_OF_FILE'
X/*
X *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
X *
X *	.newsrc parsing and update.
X */
X
X#include "config.h"
X#include "options.h"
X#include "regexp.h"
X#include "term.h"
X#include "articles.h"
X
X#define TR(arg) printf arg
X
Ximport char *news_lib_directory, *db_directory;
Ximport char *pname;
X
Ximport int  verbose;
Ximport int  silent;
X
Xexport int  keep_rc_backup = 1;
Xexport char *bak_suffix = ".bak";
X
Xexport int  no_update = 0;
Xexport int  use_selections = 1;
Xexport int  quick_unread_count = 1; /* make a quick count of unread art. */
Xexport int  newsrc_update_freq = 1; /* how often to write .newsrc */
Xexport char *newsrc_file = NULL;
X
X#define RCX_NEVER	0 /* ignore missing groups */
X#define RCX_HEAD	1 /* prepend missing groups to .newsrc when read */
X#define	RCX_TAIL	2 /* append missing groups to .newsrc when read */
X#define RCX_TIME	3 /* append NEW groups as they arrive */
X#define RCX_TIME_CONF	4 /* append NEW groups with confirmation */
X#define RCX_RNLAST	5 /* .rnlast compatible functionality */
X
Xexport int  new_group_action = RCX_TIME; /* append new groups to .newsrc */
Xexport int  keep_unsubscribed = 1; /* keep unsubscribed groups in .newsrc */
Xexport int  keep_unsub_long = 0; /* keep unread in unsubscribed groups */
X
Xexport int  tidy_newsrc = 0;	   /* remove obsolete groups from .newsrc */
X
Xexport int  auto_junk_seen = 1;	/* junk seen articles ... */
Xexport int  conf_junk_seen = 0; /* ... if confirmed by user ... */
Xexport int  retain_seen_status = 0; /* ... or remember seen articles. */
X
Xexport long unread_articles;	/* estimate of unread articles */
Xexport int  unread_groups;
X
Xexport group_header *rc_sequence = NULL;
X
Xstatic char *sel_path = NULL;
X
X
X/* delimitors on newsrc lines */
X
X#define RC_SUBSCR	':'	/* subscription to group */
X#define RC_UNSUBSCR	'!'	/* no subscription to group */
X
X#define RC_DELIM	','	/* separator on rc lines */
X#define RC_RANGE	'-'	/* range */
X
X/* delimitors on select lines */
X
X#define SEL_RANGE	'-'	/* range */
X#define SEL_SELECT	','	/* following articles are selected */
X#define SEL_LEAVE	'+'	/* following articles are left over */
X#define SEL_SEEN	';'	/* following articles are seen */
X#define SEL_UNREAD	'~'	/* in digests */
X#define SEL_DIGEST	'('	/* start digest list */
X#define SEL_END_DIGEST	')'	/* end digest list */
X#define SEL_NEW		'&'	/* new group (group.name&nnn) */
X
X#define END_OF_LIST	10000000L /* Greater than any article number */
X
X/* line buffers */
X
X#define RC_LINE_MAX	8192
X
Xstatic char rcbuf[RC_LINE_MAX];
Xstatic char selbuf[RC_LINE_MAX];
X
Xstatic group_header *rc_seq_tail = NULL;
X
Xstatic int newsrc_update_count = 0, select_update_count = 0;
X
X#define DM_NEWSRC	0
X#define DM_SELECT	1
X#define DM_ORIG_NEWSRC	2
X#define DM_ORIG_SELECT	3
X
Xstatic dump_file(path, mode)
Xchar *path;
Xint mode;
X{
X    FILE *f = NULL;
X    register group_header *gh;
X    char *line;
X
X    Loop_Groups_Newsrc(gh) {
X	switch (mode) {
X	 case DM_NEWSRC:
X	    if (tidy_newsrc) {
X		if ((gh->master_flag & M_VALID) == 0)
X		    continue;
X		if (!keep_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED))
X		    continue;
X	    }
X	    line = gh->newsrc_line;
X	    break;
X	 case DM_SELECT:
X	    if (tidy_newsrc && (gh->master_flag & M_VALID) == 0) continue;
X	    if (gh->group_flag & G_UNSUBSCRIBED) continue;
X	    line = gh->select_line;
X	    break;
X	 case DM_ORIG_NEWSRC:
X	    line = gh->newsrc_orig;
X	    break;
X	 case DM_ORIG_SELECT:
X	    line = gh->select_orig;
X	    break;
X	}
X	if (line == NULL) continue;
X	if (f == NULL)
X	    f = open_file(path, OPEN_CREATE|MUST_EXIST);
X	fputs(line, f);
X    }
X    if (f != NULL) fclose(f);
X}
X
X
Xstatic dump_newsrc()
X{
X    char bak[FILENAME];
X    static int first = 1;
X
X    if (no_update) return;
X    if (++newsrc_update_count < newsrc_update_freq) return;
X
X    if (first && keep_rc_backup) {
X	sprintf(bak, "%s%s", newsrc_file, bak_suffix);
X	dump_file(bak, DM_ORIG_NEWSRC);
X	first = 0;
X    }
X
X    dump_file(newsrc_file, DM_NEWSRC);
X
X    newsrc_update_count = 0;
X}
X
Xstatic dump_select()
X{
X    char bak[FILENAME];
X    static int first = 1;
X
X    if (no_update) return;
X    if (++select_update_count < newsrc_update_freq) return;
X
X    if (first && keep_rc_backup) {
X	sprintf(bak, "%s%s", sel_path, bak_suffix);
X	dump_file(bak, DM_ORIG_SELECT);
X	first = 0;
X    }
X
X    dump_file(sel_path, DM_SELECT);
X
X    select_update_count = 0;
X}
X
X#define RN_LAST_GROUP_READ	0
X#define RN_LAST_TIME_RUN	1
X#define RN_LAST_ACTIVE_SIZE	2
X#define RN_LAST_CREATION_TIME	3
X#define RN_LAST_NEW_GROUP	4
X#define RN_ACTIVE_TIMES_OFFSET	5
X
X#define MAX_RNLAST_LINE 6
X
Xstatic char *rnlast_line[MAX_RNLAST_LINE];
Xstatic char *rnlast_path;
X
Xstatic time_t get_last_new()
X{
X    FILE *lf = NULL;
X    char buf[FILENAME];
X    register int i;
X
X    if (new_group_action == RCX_RNLAST) {
X	rnlast_path = home_relative(".rnlast");
X	lf = open_file(rnlast_path, OPEN_READ);
X	if (lf == NULL) goto no_file;
X
X	for (i = 0; i < MAX_RNLAST_LINE; i++) {
X	    if (fgets(buf, FILENAME, lf) == NULL) break;
X	    rnlast_line[i] = copy_str(buf);
X	}
X	if (i != MAX_RNLAST_LINE) {
X	    printf(".rnlast only supported with active.times patches\n");
X	    sleep(3);
X	    new_group_action = RCX_TIME_CONF;
X	    goto no_file;
X	}
X	fclose(lf);
X	return (time_t)atol(rnlast_line[RN_LAST_CREATION_TIME]);
X    }
X
X    lf = open_file(relative(nn_directory, "LAST"), OPEN_READ);
X    if (lf == NULL) goto no_file;
X    if (fgets(buf, FILENAME, lf) == NULL) goto no_file;
X
X    fclose(lf);
X    return (time_t)atol(buf);
X
X no_file:
X    if (lf != NULL) fclose(lf);
X    return (time_t)(-1);
X}
X
Xstatic update_last_new(lastg)
Xgroup_header *lastg;
X{
X    FILE *lf = NULL;
X    register int i;
X    struct stat st;
X
X    if (new_group_action == RCX_RNLAST) {
X	lf = open_file(rnlast_path, OPEN_CREATE|MUST_EXIST);
X	fputs(rnlast_line[RN_LAST_GROUP_READ], lf);	/* as good as any */
X	fprintf(lf, "%ld\n", (long)cur_time()); /* RN_LAST_TIME_RUN */
X	fprintf(lf, "%ld\n", (long)master.last_size); /* RN_LAST_ACTIVE_SIZE */
X
X	fprintf(lf, "%ld\n", (long)lastg->creation_time); /* RN_LAST_CREATION_TIME */
X	fprintf(lf, "%s\n",lastg->group_name); /* RN_LAST_NEW_GROUP */
X
X	if (stat(relative(news_lib_directory, "active.times"), &st) == 0)
X	    fprintf(lf, "%ld\n", (long)st.st_size);
X	else /* can't be perfect -- don't update */
X	    fputs(rnlast_line[RN_ACTIVE_TIMES_OFFSET], lf);
X	for (i = 0; i < MAX_RNLAST_LINE; i++) freeobj(rnlast_line[i]);
X	freeobj(rnlast_path);
X    } else {
X	lf = open_file(relative(nn_directory, "LAST"), OPEN_CREATE|MUST_EXIST);
X	fprintf(lf, "%ld\n%s\n", (long)lastg->creation_time, lastg->group_name);
X    }
X
X    fclose(lf);
X}
X
Xstatic article_number get_last_article(gh)
Xgroup_header *gh;
X{
X    register char *line;
X
X    if ((line = gh->newsrc_line) == NULL) return -1;
X
X    line += gh->group_name_length+1;
X    while (*line && isspace(*line)) line++;
X    if (*line == NUL) return -1;
X
X    if (line[0] == '1') {
X	if (line[1] == RC_RANGE)
X	    return atol(line+2);
X	if (!isdigit(line[1])) return 1;
X    }
X    return 0;
X}
X
X
Xvisit_rc_file()
X{
X    FILE *rc, *sel;
X    register group_header *gh;
X    int subscr;
X    register char *bp;
X    register int c;
X    char bak[FILENAME];
X    time_t last_new_group = 0, rc_age, newsrc_age;
X    group_header *last_new_gh = NULL;
X
X    if (newsrc_file == NULL)
X	newsrc_file = home_relative(".newsrc");
X
X    sel_path = mk_file_name(nn_directory, "select");
X
X    Loop_Groups_Header(gh) {
X	gh->newsrc_line = NULL;
X	gh->newsrc_orig = NULL;
X	gh->select_line = NULL;
X	gh->select_orig = NULL;
X    }
X
X    if (rc_age = file_exist(relative(nn_directory, "rc"), (char *)NULL)) {
X	if (who_am_i != I_AM_NN)
X	    user_error("A release 6.3 rc file exists. Run nn to upgrade");
X
X	sprintf(bak, "%s/upgrade_rc", lib_directory);
X
X	if ((newsrc_age = file_exist(newsrc_file, (char *)NULL)) == 0) {
X	    display_file("adm.upgrade1", CLEAR_DISPLAY);
X	} else {
X	    if (rc_age + 60 > newsrc_age) {
X		/* rc file is newest (or .newsrc does not exist) */
X		display_file("adm.upgrade2", CLEAR_DISPLAY);
X		prompt("Convert rc file to .newsrc now? ");
X		if (yes(1) <= 0) nn_exit(0);
X	    } else {
X		/* .newsrc file is newest */
X		display_file("adm.upgrade3", CLEAR_DISPLAY);
X		prompt("Use current .newsrc file? ");
X		if (yes(1) > 0) {
X		    strcat(bak, " n");
X		} else {
X		    display_file("adm.upgrade4", CLEAR_DISPLAY);
X		    prompt("Convert rc file to .newsrc? ");
X		    if (yes(1) <= 0) {
X			printf("Then you will have to upgrade manually\n");
X			nn_exit(0);
X		    }
X		}
X	    }
X	}
X
X	printf("\r\n\n");
X	system(bak);
X	any_key(prompt_line);
X    }
X
X    rc = open_file(newsrc_file, OPEN_READ);
X    if (rc == NULL) goto new_user;
X
X    while (fgets(rcbuf, RC_LINE_MAX, rc) != NULL) {
X	gh = NULL;
X	subscr = 0;
X	for (bp = rcbuf; (c = *bp); bp++) {
X	    if (isspace(c)) break;	/* not a valid line */
X
X	    if (c == RC_UNSUBSCR || c == RC_SUBSCR) {
X		subscr = (c == RC_SUBSCR);
X		*bp = NUL;
X		gh = lookup(rcbuf);
X		if (gh == NULL) {
X		    gh = newobj(group_header, 1);
X		    gh->group_name = copy_str(rcbuf); /* invalid group! */
X		}
X		*bp = c;
X		break;
X	    }
X	}
X
X	if (gh == NULL) {
X	    gh = newobj(group_header, 1);
X	    gh->group_flag |= G_FAKED;
X	    gh->master_flag |= M_VALID;
X	}
X
X	if (rc_seq_tail == NULL)
X	    rc_sequence = rc_seq_tail = gh;
X	else {
X	    rc_seq_tail->newsrc_seq = gh;
X	    rc_seq_tail = gh;
X	}
X
X	gh->newsrc_orig = gh->newsrc_line = copy_str(rcbuf);
X	if (gh->group_flag & G_FAKED)
X	    gh->group_name = gh->newsrc_line;
X	else
X	    if (!subscr)
X		gh->group_flag |= G_UNSUBSCRIBED;
X    }
X    fclose(rc);
X
X new_user:
X    rc = NULL;
X    Loop_Groups_Header(gh) {
X	if (gh->master_flag & M_IGNORE_GROUP) continue;
X	if (gh->group_flag & G_UNSUBSCRIBED) continue;
X	if (gh->newsrc_line == NULL) {
X	    char buf[FILENAME];
X
X	    /* NEW GROUP - ADD TO NEWSRC AS APPROPRIATE */
X
X	    if (new_group_action == RCX_NEVER) {
X		gh->group_flag |= G_DONE; /* will not enter sequence */
X		continue;
X	    }
X
X	    switch (new_group_action) {
X	     case RCX_NEVER:
X		/* no not add new groups */
X		gh->group_flag |= G_DONE;
X		continue;
X
X	     case RCX_HEAD:
X		/* insert at top */
X		gh->newsrc_seq = rc_sequence;
X		rc_sequence = gh;
X		break;
X
X
X	     case RCX_TIME:
X	     case RCX_TIME_CONF:
X	     case RCX_RNLAST:
X		if (last_new_group == 0)
X		    last_new_group = get_last_new();
X
X		if (gh->creation_time <= last_new_group) {
X		    /* old groups not in .newsrc are unsubscribed */
X		    gh->group_flag |= G_UNSUBSCRIBED;
X		    continue;
X		}
X
X		if (last_new_gh == NULL || last_new_gh->creation_time <= gh->creation_time)
X		    last_new_gh = gh;
X
X		if (new_group_action != RCX_TIME) {
X		    printf("\nNew group: %s -- append to .newsrc? (y)");
X		    if (yes(0) <= 0) continue;
X		}
X		sprintf(buf, "%s:\n", gh->group_name);
X		/* to avoid fooling the LAST mechanism, we must fake */
X		/* that the group was also in the original .newsrc */
X
X		gh->newsrc_orig = gh->newsrc_line = copy_str(buf);
X		newsrc_update_count++;
X
X		/* fall thru */
X
X	     case RCX_TAIL:
X		/* insert at bottom */
X		if (rc_seq_tail == NULL)
X		    rc_sequence = rc_seq_tail = gh;
X		else {
X		    rc_seq_tail->newsrc_seq = gh;
X		    rc_seq_tail = gh;
X		}
X		break;
X	    }
X
X	    gh->last_article = -1;
X	} else
X	    gh->last_article = get_last_article(gh);
X
X	if (gh->last_article < 0) {
X	    gh->group_flag |= G_NEW;
X	    gh->last_article = gh->first_db_article - 1;
X	} else
X	    if (gh->first_db_article > gh->last_article)
X		gh->last_article = gh->first_db_article - 1;
X
X	if (gh->last_article < 0) gh->last_article = 0;
X	gh->first_article = gh->last_article;
X    }
X
X    if (rc_seq_tail)
X	rc_seq_tail->newsrc_seq = NULL;
X
X    if (last_new_gh != NULL)
X	update_last_new(last_new_gh);
X
X    if (!use_selections) return;
X
X    sel = open_file(sel_path, OPEN_READ);
X    if (sel == NULL) return;
X
X    while (fgets(selbuf, RC_LINE_MAX, sel) != NULL) {
X	gh = NULL;
X	for (bp = selbuf; (c = *bp); bp++)
X	    if (c == SP || c == SEL_NEW) break;
X
X	if (c == NUL) continue;
X	*bp = NUL;
X	gh = lookup(selbuf);
X	if (gh == NULL) continue;
X	*bp = c;
X	if (c == SEL_NEW) gh->group_flag |= G_NEW;
X	gh->select_orig = gh->select_line = copy_str(selbuf);
X    }
X    fclose(sel);
X}
X
X/*
X * prepare to use newsrc & select information for a specific group
X */
X
Xstatic char *rc_p;		/* pointer into newsrc_line */
Xstatic article_number rc_min;	/* current newsrc range min */
Xstatic article_number rc_max;	/* current newsrc range max */
Xstatic char rc_delim;		/* delimiter character */
X
Xstatic char *sel_p;		/* pointer into select_line */
Xstatic char *sel_initp;		/* rc_p after initialization */
Xstatic article_number sel_min;	/* current select range min */
Xstatic article_number sel_max;	/* current select range max */
Xstatic article_number sel_digest; /* current digest */
Xstatic attr_type sel_type;	/* current select range type */
Xstatic char sel_delim;		/* delimiter character */
X
X
Xuse_newsrc(gh, use_orig)
Xregister group_header *gh;
Xint use_orig;
X{
X/*    TR( ("===%s===", gh->group_name) );*/
X
X    if (use_orig) {
X	rc_p = gh->newsrc_orig;
X	sel_p = gh->select_orig;
X    } else {
X	rc_p = gh->newsrc_line;
X	sel_p = gh->select_line;
X    }
X
X    if (rc_p == NULL) {
X	rc_min = rc_max = END_OF_LIST;
X    } else {
X	rc_min = rc_max = -1;
X	rc_delim = SP;
X	rc_p += gh->group_name_length + 1;
X    }
X
X    sel_digest = 0;
X    if (sel_p == NULL) {
X	sel_min = sel_max = END_OF_LIST;
X    } else {
X	sel_p += gh->group_name_length + 1;
X	sel_min = sel_max = -1;
X	sel_delim = SP;
X    }
X}
X/*
X#define TRC(wh)  TR( ("r%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, rc_min, rc_max, rc_delim) )
X#define TSEL(wh) TR( ("s%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, sel_min, sel_max, sel_delim) )
X*/
X#define TRC(wh)
X#define TSEL(wh)
X
Xattr_type test_article(ah)
Xregister article_header *ah;
X{
X    register char *p;
X    register int c;
X    register int32 n = ah->a_number, x;
X
X    while (n > rc_max) {
X	/* get next interval from newsrc line */
X	rc_min = -1;
X	x = 0;
X	p = rc_p;
X	TRC(1);
X
X	if (*p == RC_DELIM) p++;
X	if (*p == NUL || *p == NL)
X	    rc_min = rc_max = END_OF_LIST;
X	else {
X	    for ( ; (c = *p) && c != RC_DELIM && c != NL; p++) {
X		if (c == RC_RANGE) {
X		    if (rc_min < 0)
X			rc_min = x;
X		    else
X			msg("syntax error in rc file");
X		    x = 0;
X		    continue;
X		}
X
X		if (isascii(*p) && isdigit(*p))
X		    x = x*10 + c - '0';
X	    }
X	    rc_max = x;
X	    if (rc_min < 0) rc_min = x;
X	    rc_p = p;
X	}
X    }
X    TRC(2);
X
X    if (n >= rc_min && n <= rc_max) return A_READ;
X
X    p = sel_p;
X    if (sel_digest != 0) {
X	if (n == sel_digest && (ah->flag & A_DIGEST)) {
X	    if (*sel_p == SEL_END_DIGEST) return A_READ;
X	    n = ah->fpos;
X	} else {
X	    if (n < sel_digest) return 0;
X	    while (*p && *p++ != SEL_END_DIGEST);
X	    sel_digest = 0;
X	    sel_min = sel_max = -1;
X	}
X    }
X
X    while (n > sel_max) {
X	sel_min = -1;
X	sel_type = A_SELECT;
X	x = 0;
X	TSEL(3);
X
X	for (;;) {
X	    switch (*p) {
X	     case SEL_SELECT:
X		sel_type = A_SELECT;
X		p++;
X		continue;
X	     case SEL_LEAVE:
X		sel_type = A_LEAVE;
X		p++;
X		continue;
X	     case SEL_SEEN:
X		sel_type = A_SEEN;
X		p++;
X		continue;
X	     case SEL_UNREAD:
X		sel_type = 0;
X		p++;
X		continue;
X	     case SEL_DIGEST:
X		while (*p && *p++ != SEL_END_DIGEST);
X		continue;
X	     case SEL_END_DIGEST:
X		if (sel_digest) {
X		    if (sel_digest == ah->a_number) {
X			sel_p = p;
X			return A_READ;
X		    }
X		    sel_digest = 0;
X		}
X		p++;
X		sel_type = A_SELECT;
X		continue;
X	     default:
X		break;
X	    }
X	    break;
X	}
X
X	if (*p == NUL || *p == NL) {
X	    sel_min = sel_max = END_OF_LIST;
X	    break;
X	}
X
X	for ( ; c = *p ; p++ ) {
X	    switch (c) {
X	     case '0':
X	     case '1':
X	     case '2':
X	     case '3':
X	     case '4':
X	     case '5':
X	     case '6':
X	     case '7':
X	     case '8':
X	     case '9':
X		x = x*10 + c - '0';
X		continue;
X
X	     case SEL_SELECT:
X	     case SEL_LEAVE:
X	     case SEL_SEEN:
X	     case SEL_UNREAD:
X		break;
X
X	     case SEL_RANGE:
X		if (sel_min < 0)
X		    sel_min = x;
X		else
X		    msg("syntax error in sel file");
X		x = 0;
X		continue;
X
X	     case SEL_DIGEST:
X		n = ah->a_number;
X		if (n > x) {
X		    while (*p && (*p++ != SEL_END_DIGEST));
X		    x = -1;
X		    break;
X		}
X		p++;
X		sel_digest = x;
X		if (n < sel_digest) {
X		    sel_p = p;
X		    return 0;
X		}
X		n = ah->fpos;
X		x = -1;
X		break;
X
X	     case NL:
X		if (sel_digest == 0) break;
X		/* fall thru */
X	     case SEL_END_DIGEST:
X		if (sel_digest == ah->a_number) {
X		    sel_p = p;
X		    return (ah->fpos == x) ? sel_type : A_READ;
X		}
X		sel_digest = 0;
X		x = -1;
X		break;
X	    }
X	    break;
X	}
X	sel_max = x;
X	if (sel_min < 0) sel_min = x;
X	sel_p = p;
X    }
X
X    if (n >= sel_min && n <= sel_max) return sel_type;
X
X    if (sel_digest) return A_READ; /* only read articles are not listed */
X
X    return 0;	/* unread, unseen, unselected */
X}
X
X/*
X * We only mark the articles that should remain unread
X */
X
X/*VARARGS*/
Xstatic append(va_alist)
Xva_dcl
X{
X    int x;
X    register char *p;
X    char **pp, *fmt;
X    use_vararg;
X
X    start_vararg;
X    x = va_arg1(int);
X    pp = x ? &sel_p : &rc_p;
X    p = *pp;
X    if (p > (x ? &selbuf[RC_LINE_MAX - 16] : &rcbuf[RC_LINE_MAX - 16])) {
X	msg("%s line too long", x ? "select" : ".newsrc");
X	end_vararg;
X	return;
X    }
X    fmt = va_arg2(char *);
X    vsprintf(p, fmt, va_args3toN);
X    end_vararg;
X
X    while (*p) p++;
X    *p = NL;
X    p[1] = NUL;
X    *pp = p;
X}
X
Xstatic append_range(pp, delim, rmin, rmax)
Xint pp;
Xchar delim;
Xarticle_number rmin, rmax;
X{
X    if (rmin == rmax)
X	append(pp, "%c%ld", delim, (long)rmin);
X    else
X	append(pp, "%c%ld%c%ld", delim, (long)rmin, RC_RANGE, (long)rmax);
X}
X
Xstatic int32 mark_counter;
X
Xstatic begin_rc_update(gh)
Xregister group_header *gh;
X{
X    add_unread(gh, -1);
X    mark_counter = 0;
X
X    rc_p = rcbuf;
X    rc_min = 1;
X    append(0, "%s%c", gh->group_name,
X	   gh->group_flag & G_UNSUBSCRIBED ? RC_UNSUBSCR : RC_SUBSCR);
X    rc_delim = SP;
X    sel_p = selbuf;
X    sel_min = 0;
X    sel_max = 0;
X    sel_digest = 0;
X    sel_delim = SP;
X    append(1, "%s%c", gh->group_name,
X	   (gh->group_flag & G_NEW) ? SEL_NEW : SP);
X    /* sel_initp == sep_p => empty list */
X    sel_initp = (gh->group_flag & G_NEW) ? NULL : sel_p;
X}
X
Xstatic end_rc_update(gh)
Xregister group_header *gh;
X{
X    if (rc_min <= gh->last_db_article)
X	append_range(0, rc_delim, rc_min, gh->last_db_article);
X
X    if (gh->newsrc_line != NULL && strcmp(rcbuf, gh->newsrc_line)) {
X	if (gh->newsrc_orig != gh->newsrc_line)
X	    freeobj(gh->newsrc_line);
X	gh->newsrc_line = NULL;
X    }
X
X    if (gh->newsrc_line == NULL) {
X	gh->newsrc_line = copy_str(rcbuf);
X	dump_newsrc();
X    }
X
X    if (sel_digest)
X	append(1, "%c", SEL_END_DIGEST);
X    else
X	if (sel_min)
X	    append_range(1, sel_delim, sel_min, sel_max);
X
X    if (gh->select_line) {
X	if (strcmp(selbuf, gh->select_line) == 0) goto out;
X    } else
X        if (sel_p == sel_initp) goto out;
X
X    if (gh->select_line && gh->select_orig != gh->select_line)
X	freeobj(gh->select_line);
X
X    gh->select_line = (sel_p == sel_initp) ? NULL : copy_str(selbuf);
X    dump_select();
X
X out:
X    if ((gh->last_article = get_last_article(gh)) < 0)
X	gh->last_article = 0;
X
X    gh->group_flag |= G_READ;	/* should not call update_group again */
X    if (mark_counter > 0) {
X	gh->unread_count = mark_counter;
X	add_unread(gh, 0);
X    }
X}
X
Xstatic mark_article(ah, how)
Xregister article_header *ah;
Xattr_type how;
X{
X    register article_number anum;
X    char delim;
X
X    switch (how) {
X     case A_SELECT:
X	delim = SEL_SELECT;
X	break;
X     case A_SEEN:
X	delim = SEL_SEEN;
X	break;
X     case A_LEAVE:
X     case A_LEAVE_NEXT:
X	delim = SEL_LEAVE;
X	break;
X     case 0:
X	delim = SEL_UNREAD;
X	break;
X    }
X
X    mark_counter++;
X    anum = ah->a_number;
X
X    if (rc_min < anum) {
X	append_range(0, rc_delim, rc_min, anum - 1);
X	rc_delim = RC_DELIM;
X
X	if ((ah->flag & A_DIGEST) == 0
X	    && sel_min && delim == sel_delim && sel_max == (rc_min - 1))
X	    sel_max = anum - 1;	/* expand select range over read articles */
X    }
X    rc_min = anum + 1;
X
X    if (ah->flag & A_DIGEST) {
X	if (sel_digest != anum) {
X	    if (sel_digest) {
X		append(1, "%c", SEL_END_DIGEST);
X	    } else
X		if (sel_min) {
X		    append_range(1, sel_delim, sel_min, sel_max);
X		    sel_min = 0;
X		}
X	    append(1, "%c%ld%c", SEL_SELECT, (long)anum, SEL_DIGEST);
X	    sel_digest = anum;
X	}
X
X	append(1, "%c%ld", delim, (long)ah->fpos);
X	return;
X    }
X
X    if (sel_digest) {
X	append(1, "%c", SEL_END_DIGEST);
X	sel_digest = 0;
X    }
X
X    if (sel_min) {
X	if (delim != sel_delim || delim == SEL_UNREAD) {
X	    append_range(1, sel_delim, sel_min, sel_max);
X	    sel_delim = delim;
X	    if (delim == SEL_UNREAD)
X		sel_min = 0;
X	    else
X		sel_min = anum;
X	} else
X	    sel_max = anum;
X    } else
X	if (delim != SEL_UNREAD) {
X	    sel_min = sel_max = anum;
X	    sel_delim = delim;
X	}
X}
X
Xflush_newsrc()
X{
X    newsrc_update_freq = 0;
X    if (select_update_count) dump_select();
X    if (newsrc_update_count) dump_newsrc();
X}
X
Xrestore_bak()
X{
X    if (no_update)
X	return 1;
X
X    prompt("Are you sure? ");
X    if (!yes(1)) return 0;
X
X    dump_file(newsrc_file, DM_ORIG_NEWSRC);
X
X    prompt("Restore selections? ");
X    if (yes(1)) dump_file(sel_path, DM_ORIG_SELECT);
X
X    no_update = 1;	/* so current group is not updated */
X    return 1;
X}
X
X/*
X *	Update .newsrc for one group.
X *	sort_articles(0) MUST HAVE BEEN CALLED BEFORE USE.
X */
X
Xupdate_rc(gh)
Xregister group_header *gh;
X{
X    register article_header *ah, **ahp;
X    register article_number art;
X    register int junk_seen = 0;
X
X    if (gh->group_flag & (G_FOLDER | G_FAKED)) return;
X
X    begin_rc_update(gh);
X
X    for (ahp = articles, art = 0; art < n_articles; ahp++, art++) {
X	ah = *ahp;
X	if (ah->a_group != NULL && ah->a_group != gh) continue;
X
X	switch (ah->attr) {
X	 case A_READ:
X	 case A_KILL:
X	    continue;
X
X	 case A_LEAVE:
X	 case A_LEAVE_NEXT:
X	 case A_SELECT:
X	    mark_article(ah, ah->attr);
X	    continue;
X
X	 case A_SEEN:
X	    if (junk_seen == 0) {
X		junk_seen = -1;
X		if (auto_junk_seen) {
X		    if (conf_junk_seen) {
X			prompt("\1Junk seen articles\1 ");
X			if (yes(0) > 0) junk_seen = 1;
X		    } else
X			junk_seen = 1;
X		}
X	    }
X	    if (junk_seen > 0) continue;
X	    mark_article(ah, (attr_type)(retain_seen_status ? A_SEEN : 0));
X	    continue;
X
X	 case A_AUTO_SELECT:
X	 default:
X	    mark_article(ah, (attr_type)0);
X	    continue;
X	}
X    }
X
X    end_rc_update(gh);
X}
X
Xupdate_rc_all(gh, unsub)
Xregister group_header *gh;
Xint unsub;
X{
X    if (unsub) {
X	gh->group_flag &= ~G_NEW;
X	gh->group_flag |= G_UNSUBSCRIBED;
X
X	if (!keep_unsubscribed) {
X	    add_unread(gh, -1);
X	    if (gh->newsrc_line != NULL && gh->newsrc_orig != gh->newsrc_line)
X		freeobj(gh->newsrc_line);
X	    gh->newsrc_line = NULL;
X	    return;
X	}
X	
X	if (keep_unsub_long) {
X	    update_rc(gh);
X	    return;
X	}
X    }
X
X    begin_rc_update(gh);
X    end_rc_update(gh);
X}
X
Xadd_to_newsrc(gh)
Xgroup_header *gh;
X{
X    gh->group_flag &= ~G_UNSUBSCRIBED;
X
X    if (gh->newsrc_seq != NULL || gh == rc_seq_tail) {
X	update_rc(gh);
X	return;
X    }
X    
X    rc_seq_tail->newsrc_seq = gh;
X    rc_seq_tail = gh;
X    if (gh->last_db_article > 0)
X	sprintf(rcbuf, "%s: %s%ld\n", gh->group_name,
X		gh->last_db_article > 1 ? "1-" : "",
X		(long)gh->last_db_article);
X    else
X	sprintf(rcbuf, "%s:\n", gh->group_name);
X    gh->newsrc_line = copy_str(rcbuf);
X    dump_newsrc();
X}
X
Xint32 restore_rc(gh, last)
Xregister group_header *gh;
Xarticle_number last;
X{
X    register article_number *numtab, n;
X    register attr_type *attrtab, attr;
X    register int32 at, atmax;
X    article_header ahdr;
X    int32 count;
X
X    if (last > gh->last_db_article) return 0;
X
X    if (gh->unread_count <= 0) {
X	/* no unread articles to account for -- quick update */
X	n = gh->last_db_article;	/* fake for end_rc_update */
X	gh->last_db_article = last;
X	begin_rc_update(gh);
X	end_rc_update(gh);
X	gh->last_db_article = n;
X	add_unread(gh, 1); /* not done by end_rc_update bec. mark_counter==0 */
X	return gh->unread_count;
X    }
X
X    /* there are unread articles in the group */
X    /* we must truncate rc&select lines to retain older unread articles */
X
X    atmax = at = 0;
X    numtab = NULL;
X    attrtab = NULL;
X    
X    use_newsrc(gh, 0);
X    ahdr.flag = 0;
X    count = gh->unread_count;
X
X    for (n = gh->last_article + 1; n <= last; n++) {
X	if (rc_min == END_OF_LIST) {
X	    /* current & rest is unread */
X	    last = n - 1;
X	    break;
X	}
X	ahdr.a_number = n;
X	if ((attr = test_article(&ahdr)) == A_READ) continue;
X	if (at >= atmax) {
X	    atmax += 100;
X	    numtab = resizeobj(numtab, article_number, atmax);
X	    attrtab = resizeobj(attrtab, attr_type, atmax);
X	}
X	numtab[at] = n;
X	attrtab[at] = attr;
X	at++;
X    }
X
X    begin_rc_update(gh);
X    while (--at >= 0) {
X	ahdr.a_number = *numtab++;
X	mark_article(&ahdr, *attrtab++);
X    }
X    for (n = last+1; n <= gh->last_db_article; n++) {
X	ahdr.a_number = n;
X	mark_article(&ahdr, (attr_type)0);
X    }
X    end_rc_update(gh);
X    return gh->unread_count - count;
X}
X
Xrestore_unread(gh)
Xregister group_header *gh;
X{
X    if (gh->select_line != gh->select_orig) {
X	if (gh->select_line != NULL) freeobj(gh->select_line);
X	gh->select_line = gh->select_orig;
X	dump_select();
X    }
X
X    if (gh->newsrc_orig == gh->newsrc_line) return 0;
X
X    add_unread(gh, -1);
X    if (gh->newsrc_line != NULL) freeobj(gh->newsrc_line);
X    gh->newsrc_line = gh->newsrc_orig;
X    gh->last_article = gh->first_article;
X    dump_newsrc();
X
X    add_unread(gh, 1);
X
X    return 1;
X}
X
X
Xcount_unread_articles()
X{
X    register group_header *gh;
X    long n;
X
X    unread_articles = 0;
X    unread_groups = 0;
X
X    Loop_Groups_Sequence(gh) {
X	gh->unread_count = 0;
X
X	if (gh->master_flag & M_NO_DIRECTORY) continue;
X
X	if (gh->last_db_article > gh->last_article) {
X	    n = unread_articles;
X	    add_unread(gh, 1);
X	}
X	
X	if ((gh->group_flag & G_COUNTED) == 0) continue;
X	if (verbose)
X	    printf("%6d %s\n", unread_articles - n, gh->group_name);
X    }
X}
X
X
Xprt_unread(format)
Xregister char *format;
X{
X    if (format == NULL) {
X	printf("No News (is good news)\n");
X	return;
X    }
X
X    while (*format) {
X	if (*format != '%') {
X	    putchar(*format++);
X	    continue;
X	}
X	format++;
X	switch (*format++) {
X	 case 'u':
X	    printf("%ld unread article%s", unread_articles, plural((long)unread_articles));
X	    continue;
X	 case 'g':
X	    printf("%d group%s", unread_groups, plural((long)unread_groups));
X	    continue;
X	 case 'i':
X	    printf(unread_articles == 1 ? "is" : "are");
X	    continue;
X	 case 'U':
X	    printf("%ld", unread_articles);
X	    continue;
X	 case 'G':
X	    printf("%d", unread_groups);
X	    continue;
X	}
X    }
X}
X
X
Xadd_unread(gh, mode)
Xgroup_header *gh;
Xint mode;	/* +1 => count + add, 0 => gh->unread_count, -1 => subtract */
X{
X    int32 old_count;
X    article_header ahdr;
X
X    old_count = gh->unread_count;
X
X    if (mode == 0) goto add_directly;
X
X    if (gh->group_flag & G_COUNTED) {
X	unread_articles -= gh->unread_count;
X	unread_groups --;
X	gh->unread_count = 0;
X	gh->group_flag &= ~G_COUNTED;
X    }
X
X    if (mode < 0) goto out;
X
X    if (quick_unread_count)
X	gh->unread_count = gh->last_db_article - gh->last_article;
X    else {
X	use_newsrc(gh, 0);
X	ahdr.flag = 0;
X	for (ahdr.a_number = gh->last_article + 1;
X	     ahdr.a_number <= gh->last_db_article;
X	     ahdr.a_number++) {
X	    if (rc_min == END_OF_LIST) {
X		gh->unread_count += gh->last_db_article - ahdr.a_number + 1;
X		break;
X	    }
X	    if (test_article(&ahdr) != A_READ)
X		gh->unread_count++;
X	}
X    }
X
X add_directly:
X    if (gh->unread_count <= 0) {
X	gh->unread_count = 0;
X	goto out;
X    }
X
X    if (gh->group_flag & G_UNSUBSCRIBED) goto out;
X
X    unread_articles += gh->unread_count;
X    unread_groups++;
X    gh->group_flag |= G_COUNTED;
X    
X out:
X    return old_count != gh->unread_count;
X}
X
X/*
X *	nngrep
X */
X
Xstatic int
X    grep_all = 0,
X    grep_new = 0,
X    grep_not_sequence = 0,
X    grep_pending = 0,
X    grep_read = 0,
X    grep_sequence = 0,
X    grep_unsub = 0,
X    grep_long = 0,
X    grep_patterns;
X
XOption_Description(grep_options) {
X    'a', Bool_Option(grep_all),
X    'i', Bool_Option(grep_not_sequence),
X    'n', Bool_Option(grep_new),
X    'p', Bool_Option(grep_pending),
X    'r', Bool_Option(grep_read),
X    's', Bool_Option(grep_sequence),
X    'u', Bool_Option(grep_unsub),
X    'l', Bool_Option(grep_long),
X    '\0',
X};
X
Xopt_nngrep(argc, argv)
Xint argc;
Xchar *argv[];
X{
X    grep_patterns =
X	parse_options(argc, argv, (char *)NULL, grep_options, " pattern...");
X}
X
Xdo_grep(pat)
Xchar **pat;
X{
X    register group_header *gh;
X    register regexp **re;
X    register int i;
X    int header = 1;
X
X    re = newobj(regexp *, grep_patterns);
X    for (i = 0; i < grep_patterns; i++)
X	re[i] = regcomp(pat[i]);
X
X    Loop_Groups_Sorted(gh) {
X	if (gh->master_flag & M_IGNORE_GROUP) continue;
X
X	if (grep_pending && gh->unread_count <= 0) continue;
X	if (grep_read && gh->unread_count > 0) continue;
X	if (grep_sequence && (gh->group_flag & G_SEQUENCE) == 0) continue;
X	if (grep_not_sequence && (gh->group_flag & G_SEQUENCE)) continue;
X	if (grep_new && (gh->group_flag & G_NEW) == 0) continue;
X	if (!grep_all) {
X	    if (grep_unsub && (gh->group_flag & G_UNSUBSCRIBED) == 0) continue;
X	    if (!grep_unsub && (gh->group_flag & G_UNSUBSCRIBED)) continue;
X	}
X
X	if (grep_patterns > 0) {
X	    for (i = 0; i < grep_patterns; i++)
X		if (regexec(re[i], gh->group_name)) break;
X	    if (i == grep_patterns) continue;
X	}
X
X	if (grep_long) {
X	    if (header)
X		printf("SUBSCR NEW UNREAD SEQUENCE GROUP\n");
X	    header = 0;
X
X	    printf(" %s   %s ",
X		   (gh->group_flag & G_UNSUBSCRIBED) ? "no " : "yes",
X		   (gh->group_flag & G_NEW) ? "yes" : "no ");
X
X	    if (gh->unread_count > 0)
X		printf("%6d ", gh->unread_count);
X	    else
X		printf("       ");
X	    if (gh->group_flag & G_SEQUENCE)
X		printf("  %4d   ", gh->preseq_index);
X	    else
X		printf("         ");
X	}
X
X	printf("%s\n", gh->group_name);
X    }
X}
X
X
X/*
X *	nntidy
X */
X
Xstatic int
X    tidy_unsubscribed = 0,	/* truncate lines for unsub groups*/
X    tidy_remove_unsub = 0,	/* remove lines for unsub groups*/
X    tidy_sequence = 0,		/* remove groups not in sequence */
X    tidy_ignored = 0,		/* remove G_IGN groups */
X    tidy_crap = 0,		/* remove unrecognized lines */
X    tidy_all = 0;		/* all of the above */
X
XOption_Description(tidy_options) {
X    'N', Bool_Option(no_update),
X    'Q', Bool_Option(silent),
X    'v', Bool_Option(verbose),
X    'a', Bool_Option(tidy_all),
X    'c', Bool_Option(tidy_crap),
X    'i', Bool_Option(tidy_ignored),
X    'r', Bool_Option(tidy_remove_unsub),
X    's', Bool_Option(tidy_sequence),
X    'u', Bool_Option(tidy_unsubscribed),
X    '\0',
X};
X
Xopt_nntidy(argc, argv)
Xint argc;
Xchar *argv[];
X{
X    return parse_options(argc, argv, (char *)NULL, 
X			 tidy_options, " [group]...");
X}
X
Xdo_tidy_newsrc()
X{
X    register group_header *gh;
X    int changed;
X    char *why;
X
X    /* visit_rc_file has been called. */
X
X    keep_rc_backup = 1;
X    bak_suffix = ".tidy";
X
X    tidy_newsrc = 0;
X    changed = 0;
X
X    if (tidy_all)
X	tidy_sequence = tidy_ignored = tidy_crap = tidy_unsubscribed = 1;
X
X    newsrc_update_freq = 9999;
X
X    Loop_Groups_Newsrc(gh) {
X	if ((gh->master_flag & M_VALID) == 0) {
X	    why = "Unknown group:   ";
X	    goto delete;
X	}
X	if (tidy_sequence && (gh->group_flag & G_SEQUENCE) == 0) {
X	    why = "Not in sequence: ";
X	    goto delete;
X	}
X	if (tidy_ignored && (gh->master_flag & M_IGNORE_GROUP)) {
X	    why = "Ignored group:   ";
X	    goto delete;
X	}
X	if (tidy_crap && (gh->group_flag & G_FAKED)) {
X	    why = "Crap in .newsrc: ";
X	    goto delete;
X	}
X	if (tidy_remove_unsub && (gh->group_flag & G_UNSUBSCRIBED)) {
X	    if (gh->group_flag & G_FAKED) continue;
X	    why = "Unsubscribed:    ";
X	    goto delete;
X	}
X
X	if (tidy_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED)) {
X	    if (gh->group_flag & G_FAKED) continue;
X
X	    begin_rc_update(gh);
X	    gh->last_db_article = 0;
X	    end_rc_update(gh);
X
X	    if (gh->newsrc_line != gh->newsrc_orig) {
X		why = "Truncated:       ";
X		goto change;
X	    }
X	}
X	if (verbose) {
X	    why = "Ok:              ";
X	    goto report;
X	}
X	continue;
X
X     delete:
X	gh->newsrc_line = NULL;
X	gh->select_line = NULL;
X
X     change:
X	changed = 1;
X
X     report:
X	if (!silent) printf("%s%s\n", why, gh->group_name);
X    }
X
X    if (changed) {
X	newsrc_update_freq = 0;
X	dump_newsrc();
X	dump_select();
X	printf("NOTICE: Original files are saved with %s extention\n", bak_suffix);
X    }
X}
X
X/*
X *	nngoback
X */
X
Xstatic int
X    goback_interact = 0, /* interactive nngoback */
X    goback_days = -1,
X    goback_alsounsub = 0; /* unsubscribed groups also */
X
XOption_Description(goback_options) {
X    'N', Bool_Option(no_update),
X    'Q', Bool_Option(silent),
X    'd', Int_Option(goback_days),
X    'i', Bool_Option(goback_interact),
X    'u', Bool_Option(goback_alsounsub),
X    'v', Bool_Option(verbose),
X    '\0',
X};
X
Xopt_nngoback(argc, argvp)
Xint argc;
Xchar ***argvp;
X{
X    int n;
X    
X    n = parse_options(argc, *argvp, (char *)NULL, goback_options,
X		      " days [groups]...");
X
X    if (goback_days < 0) {
X	if (n == 0 || !isdigit((*argvp)[1][0])) {
X	    fprintf(stderr, "usage: %s [-NQvi] days [groups]...\n", pname);
X	    nn_exit(1);
X	}
X	goback_days = atoi((*argvp)[1]);
X	n--;
X	++*argvp;
X    }
X    return n;
X}
X
Xdo_goback()
X{
X    char back_act[FILENAME];
X    FILE *ba;
X    register group_header *gh;
X    int32 count, total;
X    int groups, y;
X    
X    sprintf(back_act, "%s/active.%d", db_directory, goback_days);
X    if ((ba = open_file(back_act, OPEN_READ)) == NULL) {
X	fprintf(stderr, "Cannot go back %d days\n", goback_days);
X	nn_exit(1);
X    }
X
X    read_active_file(ba, (FILE *)NULL);
X
X    fclose(ba);
X
X    /* visit_rc_file has been called. */
X
X    keep_rc_backup = 1;
X    bak_suffix = ".goback";
X    newsrc_update_freq = 9999;
X    quick_unread_count = 0;
X    total = groups = 0;
X
X    if (goback_interact) {
X	init_term();
X	raw();
X    }
X
X    Loop_Groups_Sequence(gh) {
X	if ((gh->master_flag & M_VALID) == 0) continue;
X	if (!goback_alsounsub && (gh->group_flag & G_UNSUBSCRIBED)) continue;
X
X	add_unread(gh, 1);
X
X	count = restore_rc(gh, gh->last_a_article);
X	if (count > 0) {
X	    if (goback_interact) {
X		printf("%s + %ld ?  (y) ", gh->group_name, (long)count); fl;
X		y = yes(0);
X		putchar(CR); putchar(NL);
X		switch (y) {
X		 case 1:
X		    break;
X		 case 0:
X		    gh->newsrc_line = gh->newsrc_orig;
X		    gh->select_line = gh->select_orig;
X		    continue;
X		 case -1:
X		    if (total > 0) {
X			printf("\nSave changes sofar? (n) "); fl;
X			if (yes(1) <= 0) nn_exit(0);
X		    }
X		    goto out;
X		}
X	    } else
X		if (verbose)
X		    printf("%5ld\t%s\n", (long)count, gh->group_name);
X
X	    total += count;
X	    groups++;
X	}
X    }
X
X out:
X
X    if (total == 0) {
X	printf("No articles marked\n");
X	return;
X    }
X
X    flush_newsrc();
X
X    if (verbose) putchar(NL);
X    if (!silent)
X	printf("%ld article%s marked unread in %d group%s\n",
X	       (long)total, plural((long)total),
X	       groups, plural((long)groups));
X}
X
X/* fake this for read_active_file */
X
Xgroup_header *add_new_group(name)
Xchar *name;
X{
X    return NULL;
X}
END_OF_FILE
  if test 34671 -ne `wc -c <'newsrc.c'`; then
    echo shar: \"'newsrc.c'\" unpacked with wrong size!
  fi
  # end of 'newsrc.c'
fi
if test -f 'sequence.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'sequence.c'\"
else
  echo shar: Extracting \"'sequence.c'\" \(14489 characters\)
  sed "s/^X//" >'sequence.c' <<'END_OF_FILE'
X/*
X *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
X *
X *	Read presentation sequence file
X */
X
X#include "config.h"
X#include "debug.h"
X
Xexport group_header *group_sequence;
Xexport char *read_mail = NULL;
Xexport int also_subgroups = 1;
Xexport int hex_group_args = 0;
X
Xstatic int seq_break_enabled = 1;	/* !! enabled */
Xstatic int ignore_done_flag = 0; 	/* % toggle */
X
Xstatic group_header *tail_sequence = NULL;
Xstatic group_header *final_sequence = NULL;
X
Xstatic int gs_more_groups;
X
X
Xonly_folder_args(args)
Xchar **args;
X{
X    register char *arg;
X
X    while (arg = *args++) {
X	if (*arg == '+' || *arg == '~' || *arg == '/') continue;
X	if (file_exist(arg, "fr")) continue;
X	return 0;
X    }
X    return 1;
X}
X
X
X#define	SHOW_NORMAL	0	/*   : put this in at current pos */
X#define	SHOW_FIRST	1	/* < : show these groups first */
X#define	SHOW_LAST	2	/* > : show this as late as possible */
X#define	IGNORE_ALWAYS	3	/* ! : ignore these groups completely */
X#define IGN_UNLESS_RC	4	/* !:X ignore these groups unless in rc */
X#define	IGN_UNLESS_NEW	5	/* !:O ignore these groups unless new */
X#define IGN_UNL_RC_NEW	6	/* !:U ignore unsubscribed */
X#define	IGN_IF_NEW	7	/* !:N ignore these groups if new */
X
X#define	SHOW_MODES	" <>!-?*"
X
Xstatic enter_sequence(mode, gh)
Xint mode;
Xgroup_header *gh;
X{
X#ifdef SEQ_TEST
X    if (Debug & SEQ_TEST && mode != SHOW_NORMAL)
X	printf("SEQ(%c), %s\n", SHOW_MODES[mode], gh->group_name);
X#endif
X
X    if (gh->master_flag & M_IGNORE_GROUP) return 0;
X    if (ignore_done_flag) {
X	if (gh->group_flag & G_SEQUENCE) return 0;
X    } else
X	if (gh->group_flag & G_DONE) return 0;
X
X    switch (mode) {
X     case IGN_UNLESS_NEW:
X	if ((gh->group_flag & G_NEW) == 0)
X	    gh->group_flag |= G_DONE;
X	return 0;
X
X     case IGN_IF_NEW:
X	if (gh->group_flag & G_NEW)
X	    gh->group_flag |= G_DONE;
X	return 0;
X
X     case IGN_UNL_RC_NEW:
X	if (gh->group_flag & G_NEW) return 0;
X        if (gh->newsrc_line == NULL || (gh->group_flag & G_UNSUBSCRIBED))
X	    gh->group_flag |= G_DONE;
X	return 0;
X
X     case IGN_UNLESS_RC:
X        if (gh->newsrc_line == NULL || (gh->group_flag & (G_UNSUBSCRIBED|G_NEW)))
X	    gh->group_flag |= G_DONE;
X	return 0;
X
X     case IGNORE_ALWAYS:
X	gh->group_flag |= G_DONE;
X	return 0;
X
X     default:
X	gh->group_flag |= G_DONE;
X	break;
X    }
X
X    gh->group_flag |= G_SEQUENCE;
X
X    if (gh->master_flag & M_NO_DIRECTORY)
X	return 0;		/* for nntidy -s */
X
X    switch (mode) {
X     case SHOW_FIRST:
X	if (tail_sequence) {
X	    gh->next_group = group_sequence;
X	    group_sequence = gh;
X	    break;
X	}
X	/* fall thru */
X
X     case SHOW_NORMAL:
X	if (tail_sequence)
X	    tail_sequence->next_group = gh;
X	else
X	    group_sequence = gh;
X	tail_sequence = gh;
X	break;
X
X     case SHOW_LAST:
X	gh->next_group = final_sequence;
X	final_sequence = gh;
X	break;
X    }
X    return 1;
X}
X
X
Xstatic faked_entry(name, flag)
Xchar *name;
Xflag_type flag;
X{
X    group_header *gh;
X
X    gh = newobj(group_header, 1);
X
X    gh->group_name = name;
X    gh->group_flag = flag | G_FAKED;
X    gh->master_flag = 0;
X
X    /* "invent" an unread article for read_news */
X    gh->last_article = 1;
X    gh->last_db_article = 2;
X
X    enter_sequence(SHOW_NORMAL, gh);
X}
X
Xstatic end_sequence()
X{
X    register group_header *gh, *backp;
X    register int seq_ix;
X
X    if (tail_sequence)
X	tail_sequence->next_group = NULL;
X
X    /* set up backward pointers */
X
X    backp = NULL;
X    seq_ix = 0;
X    Loop_Groups_Sequence(gh) {
X	gh->preseq_index = (gh->group_flag & G_UNSUBSCRIBED) ? 0 : ++seq_ix;
X	gh->prev_group = backp;
X	backp = gh;
X    }
X
X#ifdef SEQ_DUMP
X    if (Debug & SEQ_DUMP) {
X	for (gh = group_sequence; gh; gh = gh->next_group)
X	    printf("%s\t", gh->group_name);
X	putchar(NL);
X
X	nn_exit(0);
X    }
X#endif
X
X}
X
X
X#ifdef MAIL_READING
Xstatic mail_check()
X{
X    static group_header mail_group;
X    struct stat st;
X
X    if (read_mail == NULL) return;
X    if (stat(read_mail, &st) < 0) return;
X    if (st.st_size == 0 || st.st_mtime < st.st_atime) return;
X
X    mail_group.group_name = read_mail;
X    gh->group_flag = G_FOLDER | G_MAILBOX | G_FAKED;
X    gh->master_flag = 0;
X
X    /* "invent" an unread article for read_news */
X    gh->last_article = 1;
X    gh->last_db_article = 2;
X
X
X    if (tail_sequence) {
X	mail_group.next_group = group_sequence;
X	group_sequence = mail_group;
X    } else
X	enter_sequence(SHOW_NORMAL, &mail_group);
X}
X#endif
X
X
X
Xstatic visit_presentation_file(directory, seqfile, hook)
Xchar *directory, *seqfile;
XFILE *hook;
X{
X    import int group_name_args;
X
X    register FILE *sf;
X    register c;
X    register group_header *gh;
X    group_header *mp_group, *get_group_search();
X    char group[FILENAME], *gname;
X    char savefile[FILENAME], *dflt_save, *enter_macro;
X    extern char *parse_enter_macro();
X    register char *gp;
X    int mode, merge_groups;
X
X    if (gs_more_groups == 0) return 0;
X
X    if (hook != NULL)
X	sf = hook;	/* hook to init file */
X    else
X	if ((sf = open_file(relative(directory, seqfile), OPEN_READ)) == NULL)
X	    return 0;
X
X#ifdef SEQ_TEST
X    if (Debug & SEQ_TEST)
X	printf("Sequence file %s/%s\n", directory, seqfile);
X#endif
X
X    mode = SHOW_NORMAL;
X    savefile[0] = NUL;
X
X    while (gs_more_groups) {
X
X	if ((c = getc(sf)) == EOF) break;
X	if (!isascii(c) || isspace(c)) continue;
X
X	switch (c) {
X	 case '!':
X	    mode = IGNORE_ALWAYS;
X	    if ((c = getc(sf)) == EOF) continue;
X	    if (c == '!') {
X		if (seq_break_enabled) {
X		    fclose(sf);
X		    return 1;
X		}
X		mode = SHOW_NORMAL;
X		continue;
X	    }
X	    if (c == ':') {
X		if ((c = getc(sf)) == EOF) continue;
X		if (!isascii(c) || isspace(c) || !isupper(c)) continue;
X		switch (c) {
X		 case 'O':
X		    mode = IGN_UNLESS_NEW;
X		    continue;
X		 case 'N':
X		    mode = IGN_IF_NEW;
X		    continue;
X		 case 'U':
X		    mode = IGN_UNL_RC_NEW;
X		    continue;
X		 case 'X':
X		    mode = IGN_UNLESS_RC;
X		    continue;
X		 default:
X		    /*should give error here*/
X		    mode = SHOW_NORMAL;
X		    continue;
X		}
X	    }
X	    ungetc(c, sf);
X	    continue;
X
X	 case '<':
X	    mode = SHOW_FIRST;
X	    continue;
X
X	 case '>':
X	    mode = SHOW_LAST;
X	    continue;
X
X	 case '%':
X	    ignore_done_flag = ! ignore_done_flag;
X	    continue;
X
X	 case '@':
X	    seq_break_enabled = 0;
X	    mode = SHOW_NORMAL;
X	    continue;
X
X	 case '#':
X	    do c = getc(sf);
X	    while (c != EOF && c != NL);
X	    mode = SHOW_NORMAL;
X	    continue;
X
X	}
X
X	gp = group;
X	merge_groups = 0;
X	do {
X	    *gp++ = c;
X	    if (c == ',') merge_groups = 1;
X	    c = getc(sf);
X	} while (c != EOF && isascii(c) && !isspace(c));
X
X	*gp = NUL;
X
X	while (c != EOF && (!isascii(c) || isspace(c))) c = getc(sf);
X	if (c == '+' || c == '~' || c == '/') {
X	    gp = savefile;
X	    if (c == '+') {
X		c = getc(sf);
X		if (c == EOF || (isascii(c) && isspace(c)))
X		    goto use_same_savefile;
X		*gp++ = '+';
X	    }
X	    do {
X		*gp++ = c;
X		c = getc(sf);
X	    } while (c != EOF && isascii(c) && !isspace(c));
X	    *gp = NUL;
X	    dflt_save = savefile[0] ? copy_str(savefile) : NULL;
X	} else
X	    dflt_save = NULL;
X
X     use_same_savefile:
X	while (c != EOF && (!isascii(c) || isspace(c))) c = getc(sf);
X	if (c == '(') {
X	    enter_macro = parse_enter_macro(sf, getc(sf));
X	} else {
X	    enter_macro = NULL;
X	    if (c != EOF) ungetc(c, sf);
X	}
X
X	mp_group = NULL;
X	for (gp = group; *gp;) {
X	    gname = gp;
X	    if (merge_groups) {
X		while (*gp && *gp != ',') gp++;
X		if (*gp) *gp++ = NUL;
X	    }
X	    start_group_search(gname);
X
X	    while (gh = get_group_search()) {
X		if (!enter_sequence(mode, gh)) continue;
X
X		if (merge_groups && (gh->group_flag & G_UNSUBSCRIBED) == 0) {
X		    if (mp_group == NULL) {
X			gh->group_flag |= G_MERGE_HEAD;
X		    } else {
X			mp_group->merge_with = gh;
X			gh->group_flag |= G_MERGE_SUB;
X		    }
X		    mp_group = gh;
X		}
X
X		if (gh->save_file == NULL) /* not set by "save-files" */
X		    gh->save_file = dflt_save;
X		if (gh->enter_macro == NULL) /* not set by "on entry" */
X		    gh->enter_macro = enter_macro;
X	    }
X	    if (!merge_groups) *gp = NUL;
X	}
X	if (merge_groups && mp_group != NULL)
X	    mp_group->merge_with = NULL;
X	mode = SHOW_NORMAL;
X    }
X
X    fclose(sf);
X    return 0;
X}
X
Xparse_save_files(sf)
Xregister FILE *sf;
X{
X    register c;
X    register group_header *gh;
X    group_header *get_group_search();
X    char group[FILENAME];
X    char *savefile = NULL;
X    char namebuf[FILENAME];
X    register char *gp;
X
X    for (;;) {
X	if ((c = getc(sf)) == EOF) break;
X	if (!isascii(c) || isspace(c)) continue;
X	if (c == '#') {
X	    do c = getc(sf); while (c != EOF && c != NL);
X	    continue;
X	}
X	gp = group;
X	do {
X	    *gp++ = c;
X	    c = getc(sf);
X	} while (c != EOF && isascii(c) && !isspace(c));
X	*gp = NUL;
X
X	if (strcmp(group, "end") == 0) break;
X
X	while (c != EOF && (!isascii(c) || isspace(c))) c = getc(sf);
X
X	gp = namebuf;
X	do {
X	    *gp++ = c;
X	    c = getc(sf);
X	} while (c != EOF && isascii(c) && !isspace(c));
X	*gp = NUL;
X	if (namebuf[0] == NUL) break;
X	if (strcmp(namebuf, "+"))
X	    savefile = copy_str(namebuf);
X
X	start_group_search(group);
X
X	while (gh = get_group_search())
X	    gh->save_file = savefile;
X    }
X}
X
Xnamed_group_sequence(groups)
Xchar **groups;
X{
X    register group_header *gh;
X    group_header *get_group_search();
X    register char *group;
X    int found, any, errors, gnum;
X
X    group_sequence = NULL;
X    also_subgroups = 0;
X
X    any = errors = 0;
X    while (group = *groups++) {
X
X	if (hex_group_args) {
X	    sscanf(group, "%x", &gnum);
X	    if (gnum < 0 || gnum >= master.number_of_groups) continue;
X	    gh = &active_groups[gnum];
X	    if (enter_sequence(SHOW_NORMAL, gh)) any++;
X	    continue;
X	}
X
X	if (gh = lookup(group)) {
X	    if (enter_sequence(SHOW_NORMAL, gh)) any++;
X	    continue;
X	}
X
X	if (file_exist(group, "fr")) {
X	    faked_entry(group, G_FOLDER);
X	    any++;
X	    continue;
X	}
X
X	if (*group == '+' || *group == '~') {
X	    char exp_file[FILENAME];
X	    group_header fake_group;
X
X	    current_group = &fake_group;
X	    fake_group.group_name = group;
X	    group_file_name = NULL;
X	    if (expand_file_name(exp_file, group, 1) && file_exist(exp_file, "fr")) {
X		faked_entry(copy_str(exp_file), G_FOLDER);
X		any++;
X		continue;
X	    }
X
X	    printf("Folder %s not found\n", group); fl;
X	    errors++;
X	    continue;
X	}
X
X	found = 0;
X	start_group_search(group);
X	while (gh = get_group_search()) {
X	    found++;
X	    enter_sequence(SHOW_NORMAL, gh);
X	}
X
X	if (!found) {
X	    printf("Group %s not found\n", group); fl;
X	    errors++;
X	} else
X	    any++;
X    }
X
X    end_sequence();
X
X    if (errors) user_delay(2);
X
X    return any;
X}
X
XFILE *loc_seq_hook = NULL;	/* sequence in local "init" file */
XFILE *glob_seq_hook = NULL;	/* sequence in global "init" file */
X
Xnormal_group_sequence()
X{
X    register group_header *gh;
X
X    group_sequence = NULL;
X    gs_more_groups = 1;
X
X    /* visit_p_f returns non-zero if terminated by !! */
X
X    if (visit_presentation_file(nn_directory, "seq", loc_seq_hook))
X	goto final;
X
X    if (visit_presentation_file(lib_directory, "sequence", glob_seq_hook))
X	goto final;
X
X    Loop_Groups_Sorted(gh) {
X	enter_sequence(SHOW_NORMAL, gh);
X    }
X
X final:
X    if (final_sequence)
X	if (tail_sequence) {
X	    tail_sequence->next_group = final_sequence;
X	    tail_sequence = NULL;
X	} else
X	    group_sequence = final_sequence;
X
X#ifdef MAIL_READING
X    mail_check();
X#endif
X
X    end_sequence();
X}
X
X
X
Xstatic char *gs_group;
Xstatic int gs_length, gs_index, gs_mode;
Xstatic group_header *gs_only_group = NULL;
X
X#define GS_PREFIX0	0	/* group (or group*) */
X#define	GS_PREFIX	1	/* group. */
X#define	GS_SUFFIX	2	/* .group */
X#define GS_INFIX	3	/* .group. */
X#define GS_NEW_GROUP	4	/* new group */
X#define GS_ALL		5	/* all / . */
X#define	GS_NEWSRC	6	/* RC */
X
Xstart_group_search(group)
Xchar *group;
X{
X    char *dot;
X    int last;
X    import group_header *rc_sequence;
X
X    gs_index = master.number_of_groups;	/* loop will fail */
X
X    if ((last = strlen(group) - 1) < 0) return;
X    if (group[last] == '*')
X	group[last] = NUL;
X    else
X	if (!also_subgroups && (gs_only_group = lookup(group)) != NULL)
X	    return;
X
X    gs_index = 0;
X    gs_more_groups = 0;
X    gs_length = 0;
X    gs_group = NULL;
X
X    if (strcmp(group, "NEW") == 0) {
X	gs_mode = GS_NEW_GROUP;
X	return;
X    }
X
X    if (strncmp(group, "RC", 2) == 0) {
X	gs_mode = GS_NEWSRC;
X	gs_only_group = rc_sequence;
X	gs_more_groups = 1;	/* we just can't know! */
X
X	if (group[2] != ':') return;
X	if (isdigit(group[3]))
X	    gs_index = atoi(group+3);
X	else {
X	    gs_group = group+3;
X	    gs_length = strlen(gs_group);
X	}
X	return;
X    }
X
X    if (strcmp(group, "all") == 0 || strcmp(group, ".") == 0) {
X	gs_mode = GS_ALL;
X	return;
X    }
X
X    gs_mode = GS_PREFIX0;
X
X    if (strncmp(group, "all.", 4) == 0) group += 3;
X
X    if (*group == '.') gs_mode = GS_SUFFIX;
X
X    if ((dot = strrchr(group, '.')) != NULL && dot != group) {
X	if (dot[1] == NUL || strcmp(dot+1, "all") == 0) {
X	    dot[1] = NUL;
X	    gs_mode = (gs_mode == GS_SUFFIX) ? GS_INFIX : GS_PREFIX;
X	}
X    }
X
X    gs_length = strlen(group);
X    gs_group = group;
X}
X
Xgroup_header *get_group_search()
X{
X    register group_header *gh;
X    register int c, tail;
X
X    if (gs_mode == GS_NEWSRC) {
X	do {
X	    gh = gs_only_group;
X	    if (gh == NULL) return NULL;
X	    if (gs_index && --gs_index == 0) {
X		gs_only_group = NULL;
X	    } else
X	    if (gs_group && gh->group_name_length >= gs_length &&
X		strncmp(gh->group_name, gs_group, gs_length) == 0) {
X		gs_only_group = NULL;
X	    } else
X		gs_only_group = gh->newsrc_seq;
X	} while ((!ignore_done_flag && (gh->group_flag & G_DONE)) ||
X		 (gh->master_flag & M_IGNORE_GROUP));
X	return gh;
X    }
X
X    if (gs_only_group != NULL) {
X	gh = gs_only_group;
X	gs_only_group = NULL;
X	if (!ignore_done_flag && gh->group_flag & G_DONE) return NULL;
X	if (gh->master_flag & M_IGNORE_GROUP) return NULL;
X	return gh;
X    }
X
X    while (gs_index < master.number_of_groups) {
X	gh = sorted_groups[gs_index++];
X	if (!ignore_done_flag && gh->group_flag & G_DONE) continue;
X	if (gh->master_flag & M_IGNORE_GROUP) continue;
X
X	gs_more_groups++;
X
X	if ((tail = gh->group_name_length - gs_length) < 0) continue;
X
X	switch (gs_mode) {
X
X	 case GS_NEW_GROUP:
X	    if ((gh->group_flag & G_NEW) == 0) continue;
X	    break;
X
X	 case GS_PREFIX0:
X	    if ((c = (gh->group_name)[gs_length]) != NUL && c != '.') continue;
X	 case GS_PREFIX:
X	    if (strncmp(gh->group_name, gs_group, gs_length)) continue;
X	    break;
X
X	 case GS_SUFFIX:
X	    if (strcmp(gh->group_name + tail, gs_group)) continue;
X	    break;
X
X	 case GS_INFIX:
X	    user_error(".name. notation not supported (yet)");
X	    break;
X
X	 case GS_ALL:
X	    break;
X	}
X
X	gs_more_groups--;
X	return gh;
X    }
X
X    return NULL;
X}
END_OF_FILE
  if test 14489 -ne `wc -c <'sequence.c'`; then
    echo shar: \"'sequence.c'\" unpacked with wrong size!
  fi
  # end of 'sequence.c'
fi
echo shar: End of archive 5 \(of 22\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 22 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0

exit 0 # Just in case...
