home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Unix System Administration Handbook 1997 October
/
usah_oct97.iso
/
news
/
nn.tar
/
nn-6.5.1
/
newsrc.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-08-11
|
40KB
|
1,753 lines
/*
* (c) Copyright 1990, Kim Fabricius Storm. All rights reserved.
*
* .newsrc parsing and update.
*/
#include "config.h"
#include "options.h"
#include "regexp.h"
#include "keymap.h"
#include "nn_term.h"
#include "articles.h"
/* newsrc.c */
#ifdef __STDC__
struct rc_info;
#endif
static dump_file __APROTO((char *path, int mode));
static void dump_newsrc __APROTO((void));
static void dump_select __APROTO((void));
static time_t get_last_new __APROTO((void));
static void update_last_new __APROTO((group_header *lastg));
static article_number get_last_article __APROTO((group_header *gh));
static void init_rctest __APROTO((group_header *gh, register struct rc_info *r));
static attr_type rctest __APROTO((register article_header *ah, register struct rc_info *r));
static void append __APROTO(());
static void append_range __APROTO((int pp, int delim, article_number rmin, article_number rmax));
static void begin_rc_update __APROTO((register group_header *gh));
static void end_rc_update __APROTO((register group_header *gh));
static void mark_article __APROTO((register article_header *ah, int how));
#ifdef sel
/* Remove Gould SELbus software name collision */
#undef sel
#endif
#define TR(arg) tprintf arg
import char *news_lib_directory, *db_directory;
import char *home_directory;
import char *pname;
import int verbose;
import int silent;
import long n_selected;
extern char *tmp_directory;
export int keep_rc_backup = 1;
export char *bak_suffix = ".bak";
export char *initial_newsrc_path = ".defaultnewsrc";
export int no_update = 0;
export int use_selections = 1;
export int quick_unread_count = 1; /* make a quick count of unread art. */
export int newsrc_update_freq = 1; /* how often to write .newsrc */
export char *newsrc_file = NULL;
#define RCX_NEVER 0 /* ignore missing groups */
#define RCX_HEAD 1 /* prepend missing groups to .newsrc when read */
#define RCX_TAIL 2 /* append missing groups to .newsrc when read */
#define RCX_TIME 3 /* append NEW groups as they arrive */
#define RCX_TIME_CONF 4 /* append NEW groups with confirmation */
#define RCX_RNLAST 5 /* .rnlast compatible functionality */
export int new_group_action = RCX_TIME; /* append new groups to .newsrc */
export int keep_unsubscribed = 1; /* keep unsubscribed groups in .newsrc */
export int keep_unsub_long = 0; /* keep unread in unsubscribed groups */
export int tidy_newsrc = 0; /* remove obsolete groups from .newsrc */
export int auto_junk_seen = 1; /* junk seen articles ... */
export int conf_junk_seen = 0; /* ... if confirmed by user ... */
export int retain_seen_status = 0; /* ... or remember seen articles. */
export long unread_articles; /* estimate of unread articles */
export int unread_groups;
export group_header *rc_sequence = NULL;
static char *sel_path = NULL;
/* delimitors on newsrc lines */
#define RC_SUBSCR ':' /* subscription to group */
#define RC_UNSUBSCR '!' /* no subscription to group */
#define RC_DELIM ',' /* separator on rc lines */
#define RC_RANGE '-' /* range */
/* delimitors on select lines */
#define SEL_RANGE '-' /* range */
#define SEL_SELECT ',' /* following articles are selected */
#define SEL_LEAVE '+' /* following articles are left over */
#define SEL_SEEN ';' /* following articles are seen */
#define SEL_UNREAD '~' /* in digests */
#define SEL_DIGEST '(' /* start digest list */
#define SEL_END_DIGEST ')' /* end digest list */
#define SEL_NEW '&' /* new group (group.name&nnn) */
#define END_OF_LIST 10000000L /* Greater than any article number */
/* line buffers */
#define RC_LINE_MAX 8192
static char rcbuf[RC_LINE_MAX];
static char selbuf[RC_LINE_MAX];
static group_header *rc_seq_tail = NULL;
static int newsrc_update_count = 0, select_update_count = 0;
#define DM_NEWSRC 0
#define DM_SELECT 1
#define DM_ORIG_NEWSRC 2
#define DM_ORIG_SELECT 3
static int
dump_file(path, mode)
char *path;
int mode;
{
FILE *f = NULL;
register group_header *gh;
char *line = NULL;
Loop_Groups_Newsrc(gh) {
switch (mode) {
case DM_NEWSRC:
if (tidy_newsrc) {
if ((gh->master_flag & M_VALID) == 0)
continue;
if (!keep_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED))
continue;
}
line = gh->newsrc_line;
break;
case DM_SELECT:
if (tidy_newsrc && (gh->master_flag & M_VALID) == 0) continue;
if (gh->group_flag & G_UNSUBSCRIBED) continue;
line = gh->select_line;
break;
case DM_ORIG_NEWSRC:
line = gh->newsrc_orig;
break;
case DM_ORIG_SELECT:
line = gh->select_orig;
break;
}
if (line == NULL) continue;
if (f == NULL)
f = open_file(path, OPEN_CREATE|MUST_EXIST);
fputs(line, f);
}
if (f != NULL)
if (fclose(f) == EOF) return -1;
return 0;
}
static void
dump_newsrc()
{
char bak[FILENAME];
static int first = 1;
if (no_update) return;
if (++newsrc_update_count < newsrc_update_freq) return;
if (first && keep_rc_backup) {
sprintf(bak, "%s%s", newsrc_file, bak_suffix);
if (dump_file(bak, DM_ORIG_NEWSRC))
nn_exitmsg(1, "Cannot backup %s", newsrc_file);
first = 0;
}
if (dump_file(newsrc_file, DM_NEWSRC)) {
char temp[FILENAME];
sprintf(temp, "%s/newsrc-%d", tmp_directory, process_id);
if (dump_file(temp, DM_NEWSRC))
nn_exitmsg(1, "Cannot update %s -- restore %s file!!!",
newsrc_file, bak_suffix);
else
nn_exitmsg(1, "Cannot update %s -- saved in %s", newsrc_file, temp);
}
newsrc_update_count = 0;
}
static void
dump_select()
{
char bak[FILENAME];
static int first = 1;
if (no_update) return;
if (++select_update_count < newsrc_update_freq) return;
if (first && keep_rc_backup) {
sprintf(bak, "%s%s", sel_path, bak_suffix);
dump_file(bak, DM_ORIG_SELECT);
first = 0;
}
dump_file(sel_path, DM_SELECT);
select_update_count = 0;
}
#define RN_LAST_GROUP_READ 0
#define RN_LAST_TIME_RUN 1
#define RN_LAST_ACTIVE_SIZE 2
#define RN_LAST_CREATION_TIME 3
#define RN_LAST_NEW_GROUP 4
#define RN_ACTIVE_TIMES_OFFSET 5
#define MAX_RNLAST_LINE 6
static char *rnlast_line[MAX_RNLAST_LINE];
#define rnlast_path relative(home_directory, ".rnlast")
static time_t get_last_new()
{
FILE *lf;
char buf[FILENAME];
register int i;
time_t t;
if (new_group_action == RCX_RNLAST) {
lf = open_file(rnlast_path, OPEN_READ);
if (lf == NULL) goto no_file;
for (i = 0; i < MAX_RNLAST_LINE; i++) {
if (fgets(buf, FILENAME, lf) == NULL) break;
rnlast_line[i] = copy_str(buf);
}
if (i != MAX_RNLAST_LINE) {
while (i <= MAX_RNLAST_LINE) {
rnlast_line[i] = copy_str("");
i++;
}
fclose(lf);
return (time_t)atol(rnlast_line[RN_LAST_TIME_RUN]);
}
fclose(lf);
return (time_t)atol(rnlast_line[RN_LAST_CREATION_TIME]);
}
lf = open_file(relative(nn_directory, "LAST"), OPEN_READ);
if (lf == NULL) goto no_file;
if (fgets(buf, FILENAME, lf) == NULL) goto no_file;
fclose(lf);
return (time_t)atol(buf);
no_file:
if (lf != NULL) fclose(lf);
t = file_exist(newsrc_file, (char *)NULL);
return t > 0 ? t : (time_t)(-1);
}
static void
update_last_new(lastg)
group_header *lastg;
{
FILE *lf;
register int i;
struct stat st;
if (no_update) return;
if (new_group_action == RCX_RNLAST) {
if (rnlast_line[0] == NULL)
for (i = 0; i < MAX_RNLAST_LINE; i++)
rnlast_line[i] = copy_str("");
lf = open_file(rnlast_path, OPEN_CREATE|MUST_EXIST);
fputs(rnlast_line[RN_LAST_GROUP_READ], lf); /* as good as any */
fprintf(lf, "%ld\n", (long)cur_time()); /* RN_LAST_TIME_RUN */
fprintf(lf, "%ld\n", (long)master.last_size); /* RN_LAST_ACTIVE_SIZE */
fprintf(lf, "%ld\n", (long)lastg->creation_time); /* RN_LAST_CREATION_TIME */
fprintf(lf, "%s\n",lastg->group_name); /* RN_LAST_NEW_GROUP */
if (stat(relative(news_lib_directory, "active.times"), &st) == 0)
fprintf(lf, "%ld\n", (long)st.st_size);
else /* can't be perfect -- don't update */
fputs(rnlast_line[RN_ACTIVE_TIMES_OFFSET], lf);
for (i = 0; i < MAX_RNLAST_LINE; i++) freeobj(rnlast_line[i]);
} else {
lf = open_file(relative(nn_directory, "LAST"), OPEN_CREATE|MUST_EXIST);
fprintf(lf, "%ld\n%s\n", (long)lastg->creation_time, lastg->group_name);
}
fclose(lf);
}
static article_number get_last_article(gh)
group_header *gh;
{
register char *line;
if ((line = gh->newsrc_line) == NULL) return -1;
line += gh->group_name_length+1;
while (*line && isspace(*line)) line++;
if (*line == NUL) return -1;
if (line[0] == '1') {
if (line[1] == RC_RANGE)
return atol(line+2);
if (!isdigit(line[1])) return 1;
}
return 0;
}
void
visit_rc_file()
{
FILE *rc, *sel;
register group_header *gh;
int subscr;
register char *bp;
register int c;
char bak[FILENAME];
time_t last_new_group = 0, rc_age, newsrc_age;
group_header *last_new_gh = NULL;
if (newsrc_file == NULL)
newsrc_file = home_relative(".newsrc");
sel_path = mk_file_name(nn_directory, "select");
Loop_Groups_Header(gh) {
gh->newsrc_line = NULL;
gh->newsrc_orig = NULL;
gh->select_line = NULL;
gh->select_orig = NULL;
}
if ((rc_age = file_exist(relative(nn_directory, "rc"), (char *)NULL))) {
if (who_am_i != I_AM_NN)
nn_exitmsg(1, "A release 6.3 rc file exists. Run nn to upgrade");
sprintf(bak, "%s/upgrade_rc", lib_directory);
if ((newsrc_age = file_exist(newsrc_file, (char *)NULL)) == 0) {
display_file("adm.upgrade1", CLEAR_DISPLAY);
} else {
if (rc_age + 60 > newsrc_age) {
/* rc file is newest (or .newsrc does not exist) */
display_file("adm.upgrade2", CLEAR_DISPLAY);
prompt("Convert rc file to .newsrc now? ");
if (yes(1) <= 0) nn_exit(0);
} else {
/* .newsrc file is newest */
display_file("adm.upgrade3", CLEAR_DISPLAY);
prompt("Use current .newsrc file? ");
if (yes(1) > 0) {
strcat(bak, " n");
} else {
display_file("adm.upgrade4", CLEAR_DISPLAY);
prompt("Convert rc file to .newsrc? ");
if (yes(1) <= 0)
nn_exitmsg(0, "Then you will have to upgrade manually");
}
}
}
tprintf("\r\n\n");
system(bak);
any_key(prompt_line);
}
rc = open_file(newsrc_file, OPEN_READ);
if (rc != NULL) {
fseek(rc, (off_t)0, 2);
if (ftell(rc))
rewind(rc);
else {
fclose(rc);
rc = NULL;
}
}
if (rc == NULL) {
sprintf(bak, "%s%s", newsrc_file, bak_suffix ? bak_suffix : ".bak");
if ((rc = open_file(bak, OPEN_READ))) {
int ans;
time_t rc_mtime;
tprintf("\nThere is no .newsrc file - restore backup? (y) ");
if ((ans = yes(0)) < 0) nn_exit(1);
if (!ans) {
tprintf("\nConfirm building .newsrc from scratch: (y/n) ");
if ((ans = yes(1)) <= 0) nn_exit(1);
fclose(rc);
rc = NULL;
} else {
switch (new_group_action) {
case RCX_TIME:
case RCX_TIME_CONF:
case RCX_RNLAST:
last_new_group = get_last_new();
rc_mtime = (rc ? m_time(rc) : 0);
if (last_new_group < 0 || rc_mtime < last_new_group)
last_new_group = rc_mtime;
}
}
}
}
if (rc == NULL) {
last_new_group = -1; /* ignore LAST & .rnlast if no .newsrc */
rc = open_file_search_path(initial_newsrc_path, OPEN_READ);
if (rc == NULL) goto new_user;
/* ignore groups not in the initial newsrc file */
last_new_gh = ACTIVE_GROUP(master.number_of_groups - 1);
last_new_group = last_new_gh->creation_time;
}
while (fgets(rcbuf, RC_LINE_MAX, rc) != NULL) {
gh = NULL;
subscr = 0;
for (bp = rcbuf; (c = *bp); bp++) {
if (isspace(c)) break; /* not a valid line */
if (c == RC_UNSUBSCR || c == RC_SUBSCR) {
subscr = (c == RC_SUBSCR);
*bp = NUL;
gh = lookup_no_alias(rcbuf);
*bp = c;
break;
}
}
if (gh == NULL) {
gh = newobj(group_header, 1);
gh->group_flag |= G_FAKED;
gh->master_flag |= M_VALID;
}
if (gh->master_flag & M_ALIASED) continue;
if (rc_seq_tail == NULL)
rc_sequence = rc_seq_tail = gh;
else {
rc_seq_tail->newsrc_seq = gh;
rc_seq_tail = gh;
}
gh->newsrc_orig = gh->newsrc_line = copy_str(rcbuf);
if (gh->group_flag & G_FAKED)
gh->group_name = gh->newsrc_line;
else
if (!subscr)
gh->group_flag |= G_UNSUBSCRIBED;
}
fclose(rc);
new_user:
Loop_Groups_Header(gh) {
if (gh->master_flag & M_IGNORE_GROUP) continue;
if (gh->group_flag & G_UNSUBSCRIBED) continue;
if (gh->newsrc_line == NULL) {
char buf[FILENAME];
/* NEW GROUP - ADD TO NEWSRC AS APPROPRIATE */
switch (new_group_action) {
case RCX_NEVER:
/* no not add new groups */
gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
continue;
case RCX_HEAD:
/* insert at top */
gh->newsrc_seq = rc_sequence;
rc_sequence = gh;
break;
case RCX_TIME:
case RCX_TIME_CONF:
case RCX_RNLAST:
if (last_new_group == 0) {
last_new_group = get_last_new();
if (last_new_group < 0 && new_group_action != RCX_RNLAST) {
/* maybe this is a first time rn convert ? */
int nga = new_group_action;
new_group_action = RCX_RNLAST;
last_new_group = get_last_new();
new_group_action = nga;
}
}
if (gh->creation_time <= last_new_group) {
/* old groups not in .newsrc are unsubscribed */
gh->group_flag |= G_UNSUBSCRIBED;
continue;
}
if (last_new_gh == NULL || last_new_gh->creation_time <= gh->creation_time)
last_new_gh = gh;
if (new_group_action != RCX_TIME && !no_update) {
tprintf("\nNew group: %s -- append to .newsrc? (y)",
gh->group_name);
if (yes(0) <= 0) {
gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
continue;
}
}
sprintf(buf, "%s:\n", gh->group_name);
/* to avoid fooling the LAST mechanism, we must fake */
/* that the group was also in the original .newsrc */
gh->newsrc_orig = gh->newsrc_line = copy_str(buf);
newsrc_update_count++;
/*FALLTHRU*/
case RCX_TAIL:
/* insert at bottom */
if (rc_seq_tail == NULL)
rc_sequence = rc_seq_tail = gh;
else {
rc_seq_tail->newsrc_seq = gh;
rc_seq_tail = gh;
}
break;
}
gh->last_article = -1;
} else
gh->last_article = get_last_article(gh);
if (gh->last_article < 0) {
gh->group_flag |= G_NEW;
gh->last_article = gh->first_db_article - 1;
} else
if (gh->first_db_article > gh->last_article)
gh->last_article = gh->first_db_article - 1;
if (gh->last_article < 0) gh->last_article = 0;
gh->first_article = gh->last_article;
}
if (rc_seq_tail)
rc_seq_tail->newsrc_seq = NULL;
if (last_new_gh != NULL)
update_last_new(last_new_gh);
if (!use_selections) return;
sel = open_file(sel_path, OPEN_READ);
if (sel == NULL) return;
while (fgets(selbuf, RC_LINE_MAX, sel) != NULL) {
for (bp = selbuf; (c = *bp); bp++)
if (c == SP || c == SEL_NEW) break;
if (c == NUL) continue;
*bp = NUL;
gh = lookup_no_alias(selbuf);
if (gh == NULL || gh->master_flag & M_ALIASED) continue;
*bp = c;
if (c == SEL_NEW) gh->group_flag |= G_NEW;
gh->select_orig = gh->select_line = copy_str(selbuf);
}
fclose(sel);
}
/*
* prepare to use newsrc & select information for a specific group
*/
static struct rc_info {
char *rc_p; /* pointer into newsrc_line */
article_number rc_min; /* current newsrc range min */
article_number rc_max; /* current newsrc range max */
char rc_delim; /* delimiter character */
char *sel_p; /* pointer into select_line */
char *sel_initp; /* rc_p after initialization */
article_number sel_min; /* current select range min */
article_number sel_max; /* current select range max */
article_number sel_digest; /* current digest */
attr_type sel_type; /* current select range type */
char sel_delim; /* delimiter character */
} orig, cur;
static int rctest_mode;
static void
init_rctest(gh, r)
group_header *gh;
register struct rc_info *r;
{
if (r->rc_p == NULL) {
r->rc_min = r->rc_max = END_OF_LIST;
} else {
r->rc_min = r->rc_max = -1;
r->rc_delim = SP;
r->rc_p += gh->group_name_length + 1;
}
r->sel_digest = 0;
if (r->sel_p == NULL) {
r->sel_min = r->sel_max = END_OF_LIST;
} else {
r->sel_p += gh->group_name_length + 1;
r->sel_min = r->sel_max = -1;
r->sel_delim = SP;
}
}
void
use_newsrc(gh, mode)
register group_header *gh;
int mode;
{
orig.rc_p = gh->newsrc_orig;
orig.sel_p = gh->select_orig;
cur.rc_p = gh->newsrc_line;
cur.sel_p = gh->select_line;
rctest_mode = mode;
switch (mode) {
case 0:
init_rctest(gh, &cur);
break;
case 1:
init_rctest(gh, &orig);
break;
case 2:
init_rctest(gh, &cur);
init_rctest(gh, &orig);
break;
}
}
/*
#define TRC(wh) TR( ("r%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, rc_min, rc_max, rc_delim) )
#define TSEL(wh) TR( ("s%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, sel_min, sel_max, sel_delim) )
*/
#define TRC(wh)
#define TSEL(wh)
static attr_type rctest(ah, r)
register article_header *ah;
register struct rc_info *r;
{
register char *p;
register int c;
register int32 n = ah->a_number, x;
while (n > r->rc_max) {
/* get next interval from newsrc line */
r->rc_min = -1;
x = 0;
p = r->rc_p;
TRC(1);
if (*p == RC_DELIM) p++;
if (*p == NUL || *p == NL)
r->rc_min = r->rc_max = END_OF_LIST;
else {
for ( ; (c = *p) && c != RC_DELIM && c != NL; p++) {
if (c == RC_RANGE) {
if (r->rc_min < 0)
r->rc_min = x;
else
msg("syntax error in rc file");
x = 0;
continue;
}
if (isascii(*p) && isdigit(*p))
x = x*10 + c - '0';
}
r->rc_max = x;
if (r->rc_min < 0) r->rc_min = x;
r->rc_p = p;
}
}
TRC(2);
if (n >= r->rc_min && n <= r->rc_max) return A_READ;
p = r->sel_p;
if (r->sel_digest != 0) {
if (n == r->sel_digest && (ah->flag & A_DIGEST)) {
if (*(r->sel_p) == SEL_END_DIGEST) return A_READ;
n = ah->fpos;
} else {
if (n < r->sel_digest) return 0;
while (*p && *p++ != SEL_END_DIGEST);
r->sel_digest = 0;
r->sel_min = r->sel_max = -1;
}
}
while (n > r->sel_max) {
r->sel_min = -1;
r->sel_type = A_SELECT;
x = 0;
TSEL(3);
for (;;) {
switch (*p) {
case SEL_SELECT:
r->sel_type = A_SELECT;
p++;
continue;
case SEL_LEAVE:
r->sel_type = A_LEAVE;
p++;
continue;
case SEL_SEEN:
r->sel_type = A_SEEN;
p++;
continue;
case SEL_UNREAD:
r->sel_type = 0;
p++;
continue;
case SEL_DIGEST:
while (*p && *p++ != SEL_END_DIGEST);
continue;
case SEL_END_DIGEST:
if (r->sel_digest) {
if (r->sel_digest == ah->a_number) {
r->sel_p = p;
return A_READ;
}
r->sel_digest = 0;
}
p++;
r->sel_type = A_SELECT;
continue;
default:
break;
}
break;
}
if (*p == NUL || *p == NL) {
r->sel_min = r->sel_max = END_OF_LIST;
break;
}
for ( ; (c = *p) ; p++ ) {
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
x = x*10 + c - '0';
continue;
case SEL_SELECT:
case SEL_LEAVE:
case SEL_SEEN:
case SEL_UNREAD:
break;
case SEL_RANGE:
if (r->sel_min < 0)
r->sel_min = x;
else
msg("syntax error in sel file");
x = 0;
continue;
case SEL_DIGEST:
n = ah->a_number;
if (n > x) {
while (*p && (*p++ != SEL_END_DIGEST));
x = -1;
break;
}
p++;
r->sel_digest = x;
if (n < r->sel_digest) {
r->sel_p = p;
return 0;
}
n = ah->fpos;
x = -1;
break;
case NL:
if (r->sel_digest == 0) break;
/*FALLTHRU*/
case SEL_END_DIGEST:
if (r->sel_digest == ah->a_number) {
r->sel_p = p;
return (ah->fpos == x) ? r->sel_type : A_READ;
}
r->sel_digest = 0;
x = -1;
break;
}
break;
}
r->sel_max = x;
if (r->sel_min < 0) r->sel_min = x;
r->sel_p = p;
}
if (n >= r->sel_min && n <= r->sel_max) return r->sel_type;
if (r->sel_digest) return A_READ; /* only read articles are not listed */
return 0; /* unread, unseen, unselected */
}
attr_type test_article(ah)
article_header *ah;
{
attr_type a;
switch (rctest_mode) {
case 0:
return rctest(ah, &cur);
case 1:
return rctest(ah, &orig);
case 2:
a = rctest(ah, &cur);
if (a != A_READ) return a;
if (rctest(ah, &orig) == A_READ) return A_KILL;
return a;
}
return 0;
}
/*
* We only mark the articles that should remain unread
*/
static char *rc_p; /* pointer into newsrc_line */
static article_number rc_min; /* current newsrc range min */
static char rc_delim; /* delimiter character */
static char *sel_p; /* pointer into select_line */
static char *sel_initp; /* rc_p after initialization */
static article_number sel_min; /* current select range min */
static article_number sel_max; /* current select range max */
static article_number sel_digest; /* current digest */
static char sel_delim; /* delimiter character */
/*VARARGS*/
static void
append(va_alist)
va_dcl
{
int x;
register char *p;
char **pp, *fmt;
use_vararg;
start_vararg;
x = va_arg1(int);
pp = x ? &sel_p : &rc_p;
p = *pp;
if (p > (x ? &selbuf[RC_LINE_MAX - 16] : &rcbuf[RC_LINE_MAX - 16])) {
msg("%s line too long", x ? "select" : ".newsrc");
end_vararg;
return;
}
fmt = va_arg2(char *);
vsprintf(p, fmt, va_args3toN);
end_vararg;
while (*p) p++;
*p = NL;
p[1] = NUL;
*pp = p;
}
static void
append_range(pp, delim, rmin, rmax)
int pp;
char delim;
article_number rmin, rmax;
{
if (rmin == rmax)
append(pp, "%c%ld", delim, (long)rmin);
else
append(pp, "%c%ld%c%ld", delim, (long)rmin, RC_RANGE, (long)rmax);
}
static int32 mark_counter;
static void
begin_rc_update(gh)
register group_header *gh;
{
add_unread(gh, -1);
mark_counter = 0;
rc_p = rcbuf;
rc_min = 1;
append(0, "%s%c", gh->group_name,
gh->group_flag & G_UNSUBSCRIBED ? RC_UNSUBSCR : RC_SUBSCR);
rc_delim = SP;
sel_p = selbuf;
sel_min = 0;
sel_max = 0;
sel_digest = 0;
sel_delim = SP;
append(1, "%s%c", gh->group_name,
(gh->group_flag & G_NEW) ? SEL_NEW : SP);
/* sel_initp == sep_p => empty list */
sel_initp = (gh->group_flag & G_NEW) ? NULL : sel_p;
}
static void
end_rc_update(gh)
register group_header *gh;
{
if (rc_min <= gh->last_db_article)
append_range(0, rc_delim, rc_min, gh->last_db_article);
if (gh->newsrc_line != NULL && strcmp(rcbuf, gh->newsrc_line)) {
if (gh->newsrc_orig != gh->newsrc_line)
freeobj(gh->newsrc_line);
gh->newsrc_line = NULL;
}
if (gh->newsrc_line == NULL) {
gh->newsrc_line = copy_str(rcbuf);
dump_newsrc();
}
if (sel_digest)
append(1, "%c", SEL_END_DIGEST);
else
if (sel_min)
append_range(1, sel_delim, sel_min, sel_max);
if (gh->select_line) {
if (strcmp(selbuf, gh->select_line) == 0) goto out;
} else
if (sel_p == sel_initp) goto out;
if (gh->select_line && gh->select_orig != gh->select_line)
freeobj(gh->select_line);
gh->select_line = (sel_p == sel_initp) ? NULL : copy_str(selbuf);
dump_select();
out:
if ((gh->last_article = get_last_article(gh)) < 0)
gh->last_article = 0;
if (gh->last_article < gh->first_article)
gh->first_article = gh->last_article;
gh->group_flag |= G_READ; /* should not call update_group again */
if (mark_counter > 0) {
gh->unread_count = mark_counter;
add_unread(gh, 0);
}
}
static void
mark_article(ah, how)
register article_header *ah;
attr_type how;
{
register article_number anum;
char delim = 0;
switch (how) {
case A_SELECT:
delim = SEL_SELECT;
break;
case A_SEEN:
delim = SEL_SEEN;
break;
case A_LEAVE:
case A_LEAVE_NEXT:
delim = SEL_LEAVE;
break;
case 0:
delim = SEL_UNREAD;
break;
}
mark_counter++;
anum = ah->a_number;
if (rc_min < anum) {
append_range(0, rc_delim, rc_min, anum - 1);
rc_delim = RC_DELIM;
if ((ah->flag & A_DIGEST) == 0
&& sel_min && delim == sel_delim && sel_max == (rc_min - 1))
sel_max = anum - 1; /* expand select range over read articles */
}
rc_min = anum + 1;
if (ah->flag & A_DIGEST) {
if (sel_digest != anum) {
if (sel_digest) {
append(1, "%c", SEL_END_DIGEST);
} else
if (sel_min) {
append_range(1, sel_delim, sel_min, sel_max);
sel_min = 0;
}
append(1, "%c%ld%c", SEL_SELECT, (long)anum, SEL_DIGEST);
sel_digest = anum;
}
append(1, "%c%ld", delim, (long)ah->fpos);
return;
}
if (sel_digest) {
append(1, "%c", SEL_END_DIGEST);
sel_digest = 0;
}
if (sel_min) {
if (delim != sel_delim || delim == SEL_UNREAD) {
append_range(1, sel_delim, sel_min, sel_max);
sel_delim = delim;
if (delim == SEL_UNREAD)
sel_min = 0;
else
sel_min = anum;
} else
sel_max = anum;
} else
if (delim != SEL_UNREAD) {
sel_min = sel_max = anum;
sel_delim = delim;
}
}
void
flush_newsrc()
{
newsrc_update_freq = 0;
if (select_update_count) dump_select();
if (newsrc_update_count) dump_newsrc();
}
int
restore_bak()
{
if (no_update)
return 1;
prompt("Are you sure? ");
if (!yes(1)) return 0;
if (dump_file(newsrc_file, DM_ORIG_NEWSRC))
nn_exitmsg(1, "Could not restore %s -- restore %s file manually",
newsrc_file, bak_suffix);
prompt("Restore selections? ");
if (yes(1)) dump_file(sel_path, DM_ORIG_SELECT);
no_update = 1; /* so current group is not updated */
return 1;
}
/*
* Update .newsrc for one group.
* sort_articles(-2) MUST HAVE BEEN CALLED BEFORE USE.
*/
export int rc_merged_groups_hack = 0;
void
update_rc(gh)
register group_header *gh;
{
register article_header *ah, **ahp;
register article_number art;
static int junk_seen = 0;
if (!rc_merged_groups_hack) junk_seen = 0;
if (gh->group_flag & (G_FOLDER | G_FAKED)) return;
begin_rc_update(gh);
for (ahp = articles, art = 0; art < n_articles; ahp++, art++) {
ah = *ahp;
if (ah->a_group != NULL && ah->a_group != gh) continue;
switch (ah->attr) {
case A_READ:
case A_KILL:
continue;
case A_LEAVE:
case A_LEAVE_NEXT:
case A_SELECT:
mark_article(ah, ah->attr);
continue;
case A_SEEN:
if (junk_seen == 0) {
junk_seen = -1;
if (auto_junk_seen) {
if (conf_junk_seen) {
prompt("\1Junk seen articles\1 ");
if (yes(0) > 0) junk_seen = 1;
} else
junk_seen = 1;
}
}
if (junk_seen > 0) continue;
mark_article(ah, (attr_type)(retain_seen_status ? A_SEEN : 0));
continue;
case A_AUTO_SELECT:
default:
mark_article(ah, (attr_type)0);
continue;
}
}
end_rc_update(gh);
}
void
update_rc_all(gh, unsub)
register group_header *gh;
int unsub;
{
if (unsub) {
gh->group_flag &= ~G_NEW;
gh->group_flag |= G_UNSUBSCRIBED;
if (!keep_unsubscribed) {
add_unread(gh, -1);
if (gh->newsrc_line != NULL && gh->newsrc_orig != gh->newsrc_line)
freeobj(gh->newsrc_line);
gh->newsrc_line = NULL;
if (gh->newsrc_orig != NULL) dump_newsrc();
if (gh->select_line != NULL && gh->newsrc_orig != gh->select_line)
freeobj(gh->select_line);
gh->select_line = NULL;
if (gh->select_orig != NULL) dump_select();
return;
}
if (keep_unsub_long) {
update_rc(gh);
return;
}
}
begin_rc_update(gh);
end_rc_update(gh);
}
void
add_to_newsrc(gh)
group_header *gh;
{
gh->group_flag &= ~G_UNSUBSCRIBED;
if (gh->newsrc_seq != NULL || gh == rc_seq_tail) {
update_rc(gh);
return;
}
rc_seq_tail->newsrc_seq = gh;
rc_seq_tail = gh;
if (gh->last_db_article > 0)
sprintf(rcbuf, "%s: %s%ld\n", gh->group_name,
gh->last_db_article > 1 ? "1-" : "",
(long)gh->last_db_article);
else
sprintf(rcbuf, "%s:\n", gh->group_name);
gh->newsrc_line = copy_str(rcbuf);
dump_newsrc();
}
int32 restore_rc(gh, last)
register group_header *gh;
article_number last;
{
register article_number *numtab, n;
register attr_type *attrtab, attr;
register int32 at, atmax;
article_header ahdr;
int32 count;
if (last > gh->last_db_article) return 0;
if (gh->unread_count <= 0) {
/* no unread articles to account for -- quick update */
n = gh->last_db_article; /* fake for end_rc_update */
gh->last_db_article = last;
begin_rc_update(gh);
end_rc_update(gh);
gh->last_db_article = n;
add_unread(gh, 1); /* not done by end_rc_update bec. mark_counter==0 */
return gh->unread_count;
}
/* there are unread articles in the group */
/* we must truncate rc&select lines to retain older unread articles */
atmax = at = 0;
numtab = NULL;
attrtab = NULL;
use_newsrc(gh, 0);
ahdr.flag = 0;
count = gh->unread_count;
for (n = gh->last_article + 1; n <= last; n++) {
if (cur.rc_min == END_OF_LIST) {
/* current & rest is unread */
last = n - 1;
break;
}
ahdr.a_number = n;
if ((attr = rctest(&ahdr, &cur)) == A_READ) continue;
if (at >= atmax) {
atmax += 100;
numtab = resizeobj(numtab, article_number, atmax);
attrtab = resizeobj(attrtab, attr_type, atmax);
}
numtab[at] = n;
attrtab[at] = attr;
at++;
}
begin_rc_update(gh);
while (--at >= 0) {
ahdr.a_number = *numtab++;
mark_article(&ahdr, *attrtab++);
}
for (n = last+1; n <= gh->last_db_article; n++) {
ahdr.a_number = n;
mark_article(&ahdr, (attr_type)0);
}
end_rc_update(gh);
return gh->unread_count - count;
}
int
restore_unread(gh)
register group_header *gh;
{
if (gh->select_line != gh->select_orig) {
if (gh->select_line != NULL) freeobj(gh->select_line);
gh->select_line = gh->select_orig;
dump_select();
}
if (gh->newsrc_orig == gh->newsrc_line) return 0;
add_unread(gh, -1);
if (gh->newsrc_line != NULL) freeobj(gh->newsrc_line);
gh->newsrc_line = gh->newsrc_orig;
gh->last_article = gh->first_article;
dump_newsrc();
add_unread(gh, 1);
return 1;
}
void
count_unread_articles()
{
register group_header *gh;
long n = 0;
unread_articles = 0;
unread_groups = 0;
Loop_Groups_Sequence(gh) {
gh->unread_count = 0;
if (gh->master_flag & M_NO_DIRECTORY) continue;
if (gh->last_db_article > gh->last_article) {
n = unread_articles;
add_unread(gh, 1);
}
if ((gh->group_flag & G_COUNTED) == 0) continue;
if (verbose)
tprintf("%6d %s\n", unread_articles - n, gh->group_name);
}
}
void
prt_unread(format)
register char *format;
{
if (format == NULL) {
tprintf("No News (is good news)\n");
return;
}
while (*format) {
if (*format != '%') {
tputc(*format++);
continue;
}
format++;
switch (*format++) {
case 'u':
tprintf("%ld unread article%s", unread_articles, plural((long)unread_articles));
continue;
case 'g':
tprintf("%d group%s", unread_groups, plural((long)unread_groups));
continue;
case 'i':
tprintf(unread_articles == 1 ? "is" : "are");
continue;
case 'U':
tprintf("%ld", unread_articles);
continue;
case 'G':
tprintf("%d", unread_groups);
continue;
}
}
}
int
add_unread(gh, mode)
group_header *gh;
int mode; /* +1 => count + add, 0 => gh->unread_count, -1 => subtract */
{
int32 old_count;
article_header ahdr;
old_count = gh->unread_count;
if (mode == 0) goto add_directly;
if (gh->group_flag & G_COUNTED) {
unread_articles -= gh->unread_count;
unread_groups --;
gh->unread_count = 0;
gh->group_flag &= ~G_COUNTED;
}
if (mode < 0) goto out;
if (quick_unread_count)
gh->unread_count = gh->last_db_article - gh->last_article;
else {
use_newsrc(gh, 0);
ahdr.flag = 0;
for (ahdr.a_number = gh->last_article + 1;
ahdr.a_number <= gh->last_db_article;
ahdr.a_number++) {
if (cur.rc_min == END_OF_LIST) {
gh->unread_count += gh->last_db_article - ahdr.a_number + 1;
break;
}
if (rctest(&ahdr, &cur) != A_READ)
gh->unread_count++;
}
}
add_directly:
if (gh->unread_count <= 0) {
gh->unread_count = 0;
goto out;
}
if (gh->group_flag & G_UNSUBSCRIBED) goto out;
unread_articles += gh->unread_count;
unread_groups++;
gh->group_flag |= G_COUNTED;
out:
return old_count != gh->unread_count;
}
/*
* nngrep
*/
static int
grep_all = 0,
grep_new = 0,
grep_not_sequence = 0,
grep_pending = 0,
grep_read = 0,
grep_sequence = 0,
grep_unsub = 0,
grep_long = 0,
grep_patterns;
Option_Description(grep_options) {
'a', Bool_Option(grep_all),
'i', Bool_Option(grep_not_sequence),
'n', Bool_Option(grep_new),
'p', Bool_Option(grep_pending),
'r', Bool_Option(grep_read),
's', Bool_Option(grep_sequence),
'u', Bool_Option(grep_unsub),
'l', Bool_Option(grep_long),
'\0',
};
void
opt_nngrep(argc, argv)
int argc;
char *argv[];
{
grep_patterns =
parse_options(argc, argv, (char *)NULL, grep_options, " pattern...");
}
void
do_grep(pat)
char **pat;
{
register group_header *gh;
register regexp **re = NULL;
register int i;
int header = 1;
if (grep_patterns > 0) {
re = newobj(regexp *, grep_patterns);
for (i = 0; i < grep_patterns; i++)
re[i] = regcomp(pat[i]);
}
Loop_Groups_Sorted(gh) {
if (gh->master_flag & M_IGNORE_GROUP) continue;
if (grep_pending && gh->unread_count <= 0) continue;
if (grep_read && gh->unread_count > 0) continue;
if (grep_sequence && (gh->group_flag & G_SEQUENCE) == 0) continue;
if (grep_not_sequence && (gh->group_flag & G_SEQUENCE)) continue;
if (grep_new && (gh->group_flag & G_NEW) == 0) continue;
if (!grep_all) {
if (grep_unsub && (gh->group_flag & G_UNSUBSCRIBED) == 0) continue;
if (!grep_unsub && (gh->group_flag & G_UNSUBSCRIBED)) continue;
}
if (grep_patterns > 0) {
for (i = 0; i < grep_patterns; i++)
if (regexec(re[i], gh->group_name)) break;
if (i == grep_patterns) continue;
}
if (grep_long) {
if (header)
tprintf("SUBSCR IN_RC NEW UNREAD SEQUENCE GROUP\n");
header = 0;
tprintf(" %s %s %s ",
(gh->group_flag & G_UNSUBSCRIBED) ? "no " : "yes",
(gh->newsrc_line == NULL) ? "no " : "yes",
(gh->group_flag & G_NEW) ? "yes" : "no ");
if (gh->unread_count > 0)
tprintf("%6d ", gh->unread_count);
else
tprintf(" ");
if (gh->group_flag & G_SEQUENCE)
tprintf(" %4d ", gh->preseq_index);
else
tprintf(" ");
}
tprintf("%s\n", gh->group_name);
}
}
/*
* nntidy
*/
static int
tidy_unsubscribed = 0, /* truncate lines for unsub groups*/
tidy_remove_unsub = 0, /* remove lines for unsub groups*/
tidy_sequence = 0, /* remove groups not in sequence */
tidy_ignored = 0, /* remove G_IGN groups */
tidy_crap = 0, /* remove unrecognized lines */
tidy_all = 0; /* all of the above */
Option_Description(tidy_options) {
'N', Bool_Option(no_update),
'Q', Bool_Option(silent),
'v', Bool_Option(verbose),
'a', Bool_Option(tidy_all),
'c', Bool_Option(tidy_crap),
'i', Bool_Option(tidy_ignored),
'r', Bool_Option(tidy_remove_unsub),
's', Bool_Option(tidy_sequence),
'u', Bool_Option(tidy_unsubscribed),
'\0',
};
int
opt_nntidy(argc, argv)
int argc;
char *argv[];
{
return parse_options(argc, argv, (char *)NULL,
tidy_options, " [group]...");
}
void
do_tidy_newsrc()
{
register group_header *gh;
int changed;
char *why;
/* visit_rc_file has been called. */
keep_rc_backup = 1;
bak_suffix = ".tidy";
tidy_newsrc = 0;
changed = 0;
if (tidy_all)
tidy_sequence = tidy_ignored = tidy_crap = tidy_unsubscribed = 1;
newsrc_update_freq = 9999;
Loop_Groups_Newsrc(gh) {
if ((gh->master_flag & M_VALID) == 0) {
why = "Unknown group: ";
goto delete;
}
if (tidy_sequence && (gh->group_flag & G_SEQUENCE) == 0) {
why = "Not in sequence: ";
goto delete;
}
if (tidy_ignored && (gh->master_flag & M_IGNORE_GROUP)) {
why = "Ignored group: ";
goto delete;
}
if (tidy_crap && (gh->group_flag & G_FAKED)) {
why = "Crap in .newsrc: ";
goto delete;
}
if (tidy_remove_unsub && (gh->group_flag & G_UNSUBSCRIBED)) {
if (gh->group_flag & G_FAKED) continue;
why = "Unsubscribed: ";
goto delete;
}
if (tidy_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED)) {
if (gh->group_flag & G_FAKED) continue;
begin_rc_update(gh);
gh->last_db_article = 0;
end_rc_update(gh);
if (gh->newsrc_line != gh->newsrc_orig) {
why = "Truncated: ";
goto change;
}
}
if (verbose) {
why = "Ok: ";
goto report;
}
continue;
delete:
gh->newsrc_line = NULL;
gh->select_line = NULL;
change:
changed = 1;
report:
if (!silent) tprintf("%s%s\n", why, gh->group_name);
}
if (no_update) {
if (changed) tprintf("Files were NOT updated\n");
return;
}
if (changed) {
newsrc_update_freq = 0;
dump_newsrc();
dump_select();
tprintf("NOTICE: Original files are saved with %s extention\n", bak_suffix);
}
}
/*
* nngoback
*/
static int
goback_interact = 0, /* interactive nngoback */
goback_days = -1,
goback_alsounsub = 0; /* unsubscribed groups also */
Option_Description(goback_options) {
'N', Bool_Option(no_update),
'Q', Bool_Option(silent),
'd', Int_Option(goback_days),
'i', Bool_Option(goback_interact),
'u', Bool_Option(goback_alsounsub),
'v', Bool_Option(verbose),
'\0',
};
int
opt_nngoback(argc, argvp)
int argc;
char ***argvp;
{
int n;
n = parse_options(argc, *argvp, (char *)NULL, goback_options,
" days [groups]...");
if (goback_days < 0) {
if (n == 0 || !isdigit((*argvp)[1][0])) {
fprintf(stderr, "usage: %s [-NQvi] days [groups]...\n", pname);
nn_exit(1);
}
goback_days = atoi((*argvp)[1]);
n--;
++*argvp;
}
return n;
}
void
do_goback()
{
char back_act[FILENAME];
FILE *ba;
register group_header *gh;
int32 count, total;
int groups, y;
sprintf(back_act, "%s/active.%d", db_directory, goback_days);
if ((ba = open_file(back_act, OPEN_READ)) == NULL) {
fprintf(stderr, "Cannot go back %d days\n", goback_days);
nn_exit(1);
}
read_active_file(ba, (FILE *)NULL);
fclose(ba);
/* visit_rc_file has been called. */
keep_rc_backup = 1;
bak_suffix = ".goback";
newsrc_update_freq = 9999;
quick_unread_count = 0;
total = groups = 0;
if (goback_interact) {
if (no_update) tprintf("Warning: changes will not be saved\n");
init_term(1);
nn_raw();
}
Loop_Groups_Sequence(gh) {
if ((gh->master_flag & M_VALID) == 0) continue;
if (!goback_alsounsub && (gh->group_flag & G_UNSUBSCRIBED)) continue;
add_unread(gh, 1);
count = restore_rc(gh, gh->last_a_article);
if (count > 0) {
if (goback_interact) {
tprintf("%s + %ld ? (y) ", gh->group_name, (long)count); fl;
y = yes(0);
tputc(CR); tputc(NL);
switch (y) {
case 1:
break;
case 0:
gh->newsrc_line = gh->newsrc_orig;
gh->select_line = gh->select_orig;
continue;
case -1:
if (total > 0) {
tprintf("\nSave changes sofar? (n) "); fl;
if (yes(1) <= 0) nn_exit(0);
}
goto out;
}
} else
if (verbose)
tprintf("%5ld\t%s\n", (long)count, gh->group_name);
total += count;
groups++;
}
}
out:
if (goback_interact)
unset_raw();
if (total == 0) {
tprintf("No articles marked\n");
return;
}
flush_newsrc();
if (verbose) tputc(NL);
if (!silent)
tprintf("%ld article%s marked unread in %d group%s\n",
(long)total, plural((long)total),
groups, plural((long)groups));
if (no_update) tprintf("No files were updated\n");
}
/* fake this for read_active_file */
group_header *add_new_group(name)
char *name;
{
return NULL;
}