ftp.nice.ch/pub/next/unix/network/news/nn.6.4.16.s.tar.gz#/nn/group.c

This is group.c in view mode; [Download] [Up]

/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	News group access.
 */

#include "config.h"
#include "articles.h"
#include "db.h"
#include "term.h"
#include "menu.h"
#include "keymap.h"
#include "regexp.h"
#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif

export int  dont_split_digests = 0;
export int  dont_sort_articles = 0;
export int  also_cross_postings = 0;
export int  also_unsub_groups = 0;
export int  entry_message_limit = 400;
export int  merge_report_rate = 1;

import int  article_limit, also_read_articles;
import int  no_update, novice, case_fold_search;
import int  merged_menu, keep_unsubscribed, keep_unsub_long;
import int  killed_articles;
import int  seq_cross_filtering;
import char *default_save_file, *folder_save_file;

import char delayed_msg[];
import int32 db_read_counter;

/*
 * completion of group name
 */

group_completion(hbuf, ix)
char *hbuf;
int ix;
{
    static group_number next_group, n1, n2;
    static char *head, *tail, *last;
    static int  tail_offset, prev_lgt, l1, l2;
    static group_header *prev_group, *p1, *p2;
    register group_header *gh;
    register char *t1, *t2;
    int order;

    if (ix < 0) return 0;

    if (hbuf) {
	n2 = next_group = 0;
	p2 = prev_group = NULL;
	l2 = 0;

	if (head = strrchr(hbuf, ','))
	    head++;
	else
	    head = hbuf;
	tail = hbuf + ix;
	tail_offset = ix - (head - hbuf);
	if (last = strrchr(head, '.')) last++; else last = head;
	return 1;
    }

    if (ix) {
	n1 = next_group, p1 = prev_group, l1 = prev_lgt;
	next_group = n2, prev_group = p2, prev_lgt = l2;
	list_completion((char *)NULL);
    }

    *tail = NUL;

    while (next_group < master.number_of_groups) {
	gh = sorted_groups[next_group++];
	if (gh->master_flag & M_IGNORE_GROUP) continue;
	if (gh->group_name_length <= tail_offset) continue;

	if (prev_group &&
	    strncmp(prev_group->group_name, gh->group_name, prev_lgt) == 0)
	    continue;

	order = strncmp(gh->group_name, head, tail_offset);
	if (order < 0) continue;
	if (order > 0) break;

	t1 = gh->group_name + tail_offset;
	if (t2 = strchr(t1, '.')) {
	    strncpy(tail, t1, t2 - t1 + 1);
	    tail[t2 - t1 + 1] = NUL;
	} else
	    strcpy(tail, t1);

	prev_group = gh;
	prev_lgt = tail_offset + strlen(tail);
	if (ix) {
	    if (list_completion(last) == 0) break;
	} else
	    return 1;
    }

    if (ix) {
	n2 = next_group, p2 = prev_group, l2 = prev_lgt;
	if (n2 > master.number_of_groups)
	    n2 = 0, p2 = NULL, l2 = 0;
	next_group = n1, prev_group = p1, prev_lgt = l1;
	return 1;
    }

    next_group = 0;
    prev_group = NULL;
    return 0;
}

static int group_level = 0;
static article_number entry_first_article;
static int only_unread_articles;	/* menu contains only unread art. */

static print_header()
{
    extern long unread_articles;
    extern int unread_groups;

    so_printxy(0, 0, "Newsgroup: %s", current_group->group_name);

    if (current_group->group_flag & G_MERGED) printf("    MERGED");

    clrline();

    so_gotoxy(-1, 0, 0);

    so_printf("Articles: %d", n_articles);

    if (no_update) {
	so_printf(" NO UPDATE");
    } else {
	if ((current_group->group_flag & G_MERGED) == 0 &&
	    current_group->current_first > entry_first_article)
	    so_printf((killed_articles > 0) ? "(L-%ld,K-%d)" : "(L-%ld)",
		      current_group->current_first - entry_first_article,
		      killed_articles);
	else
	    if (killed_articles > 0)
		so_printf(" (K-%d)", killed_articles);

	if (unread_articles > 0) {
	    so_printf(" of %ld", unread_articles);
	    if (unread_groups > 0)
		so_printf("/%d", unread_groups);
	}

	if (current_group->group_flag & G_UNSUBSCRIBED)
	    so_printf(" UNSUB");
	else if (current_group->group_flag & G_NEW)
	    so_printf(" NEW");

	if (current_group->unread_count <= 0)
	    so_printf(" READ");

	if (group_level > 1)
	    so_printf(" *NO*UPDATE*");
    }

    so_end();

    return 1;	/* number of lines in header */
}

/*
 * interpretation of first_art:
 *	>0	Read articles first_art..last_db_article (also read)
 *	-1	Read unread or read articles accoring to -x and -aN flags
 */

group_menu(gh, first_art, access_mode, mask, menu)
register group_header *gh;
article_number first_art;
register flag_type access_mode;
char *mask;
fct_type menu;
{
    int status, was_unread, unread_at_reentry = 0;
    int menu_cmd, o_killed;
    article_number prev_last, n;
    register group_header *mg_head;
    flag_type entry_access_mode = access_mode;
    extern flag_type parse_access_flags();
    article_number o_entry_first;
    int o_only_unread;
    import rc_merged_groups_hack;

#define	menu_return(cmd) { menu_cmd = (cmd); goto menu_exit; }

    o_entry_first = entry_first_article;
    o_only_unread = only_unread_articles;
    o_killed = killed_articles;
    mark_var_stack();

    mg_head = (group_level == 0 && gh->group_flag & G_MERGE_HEAD) ? gh : NULL;
    group_level++;

 reenter:
    for (; gh; gh = gh->merge_with) {
	if ((gh->master_flag & M_IGNORE_GROUP) || init_group(gh) <= 0) {
	    if (mg_head != NULL) continue;
	    menu_return( ME_NEXT );
	}

	if (unread_at_reentry) restore_unread(gh);

	m_invoke(-1);

	access_mode = entry_access_mode;
	if (access_mode & ACC_PARSE_VARIABLES)
	    access_mode |= parse_access_flags();

	killed_articles = 0;

	/* don't lose (a few) new articles when reentering a read group */
	/* (the user will expect the menu to be the same, and may overlook */
	/* the new articles) */
	if (gh->group_flag & G_READ) goto update_unsafe;

#ifdef RENUMBER_DANGER
	prev_last = gh->last_db_article;
#endif
	was_unread = add_unread(gh, -1);

	switch (update_group(gh)) {
	 case -1:
	    status = -1;
	    goto access_exception;
	 case -3:
	    return ME_QUIT;
	}

#ifdef RENUMBER_DANGER
	if (gh->last_db_article < prev_last) {
	    /* expire + renumbering */
	    flag_type updflag;

	    if ((gh->last_article = gh->first_db_article - 1) < 0)
		gh->last_article = 0;
	    gh->first_article = gh->last_article;
	    updflag = gh->group_flag & G_READ;
	    gh->group_flag &= ~G_READ;
	    update_rc_all(gh, 0);
	    gh->group_flag &= ~G_READ;
	    gh->group_flag |= updflag;
	}
#endif

	if (was_unread)
	    add_unread(gh, 1);

     update_unsafe:
	if ((gh->current_first = first_art) < 0) {
	    if (access_mode & (ACC_ORIG_NEWSRC | ACC_MERGED_NEWSRC))
		gh->current_first = gh->first_article + 1;
	    else if (access_mode & ACC_ALSO_READ_ARTICLES)
		gh->current_first = gh->first_db_article;
	    else
		gh->current_first = gh->last_article + 1;
	}
	
	if (gh->current_first <= 0) gh->current_first = 1;
	entry_first_article = gh->current_first;
	only_unread_articles = (access_mode & ACC_ALSO_READ_ARTICLES) == 0;
	
	if (article_limit > 0) {
	    n = gh->last_db_article - article_limit + 1;
	    if (gh->current_first < n) gh->current_first = n;
	}

	if (mg_head != NULL) {
	    access_mode |= ACC_MERGED_MENU | ACC_DONT_SORT_ARTICLES;
	    if (mg_head != gh) access_mode |= ACC_EXTRA_ARTICLES;
	}

	if (entry_message_limit &&
	    (n = gh->last_db_article - gh->current_first + 1) >= entry_message_limit) {
	    clrdisp();
	    printf("Reading %s: %ld articles...", gh->group_name, (long)n);
	} else
	    home();
	fl;

	status = access_group(gh, gh->current_first, gh->last_db_article,
			      access_mode, mask);

     access_exception:

	if (status < 0) {
	    if (status == -2) {
		clrdisp();
		printf("Read error on group %s (NFS timeout?)\n\r",
		       current_group->group_name);
		printf("Skipping to next group....  (interrupt to quit)\n\r");
		if (any_key(0) == K_interrupt) nn_exit(0);
	    }

	    if (status <= -10) {
		clrdisp();
		msg("DATABASE CORRUPTED FOR GROUP %s", gh->group_name);
#ifdef HAVE_SYSLOG
		openlog("nn", LOG_CONS, LOG_DAEMON);
		syslog(LOG_ALERT, "database corrupted for newsgroup %s.",
		       gh->group_name);
		closelog();
#endif
		user_delay(5);
	    }
	    gh->master_flag |= M_BLOCKED;
	    if (mg_head != NULL) continue;
	    menu_return( ME_NEXT );
	}

	if (mg_head == NULL) break;
    }

    if (n_articles == 0) {
	m_break_entry();
	menu_return( ME_NO_ARTICLES );
    }

    if (mg_head != NULL) {
	if (dont_sort_articles)
	    no_sort_articles();
	else
	    sort_articles(-1);
	init_group(mg_head);
    }

 samemenu:
    menu_cmd = CALL(menu)(print_header);

    if (menu_cmd == ME_REENTER_GROUP) {
	if (group_level != 1) {
	    strcpy(delayed_msg, "can only unread groups at topmost level");
	    goto samemenu;
	}
	free_memory();
	if (mg_head) gh = mg_head;
	unread_at_reentry = 1;
	goto reenter;
    }

 menu_exit:

    n = 1;			/* must unsort articles */
    if (mg_head != NULL) gh = mg_head;

    do {
	gh->current_first = 0;
	if (gh->master_flag & M_BLOCKED) continue;
	if (mask != NULL) continue;
	if (access_mode & ACC_ALSO_READ_ARTICLES || first_art >= 0) continue;
	if (menu_cmd != ME_NO_ARTICLES) gh->group_flag &= ~G_NEW;
	if (gh->group_flag & G_UNSUBSCRIBED && !keep_unsub_long) continue;
	if (n) sort_articles(-2);
	n = 0;
	update_rc(gh);
	rc_merged_groups_hack = 1;
    } while (mg_head != NULL && (gh = gh->merge_with) != NULL);

    rc_merged_groups_hack = 0;

    killed_articles = o_killed;
    entry_first_article = o_entry_first;
    only_unread_articles = o_only_unread;
    restore_variables();
    group_level--;

    return menu_cmd;
}

static l_re_query(pr, gh)
char *pr;
group_header *gh;
{
    if (pr == NULL) return 1;

    prompt("\1%s\1 %s ? ", pr, gh->group_name);
    return yes(0);
}

group_header *lookup_regexp(name, pr, flag)
char *name, *pr;
int flag;	/* 1=>seq order, 2=>msg(err) */
{
    group_header *gh;
    regexp *re;
    int y, any;
    char *err;

    if (gh = lookup(name)) return gh;

    if ((re = regcomp(name)) == NULL) return NULL;
    y = any = 0;
    
    if (pr && (flag & 1)) m_advinput();

    if (flag & 1)
	Loop_Groups_Sequence(gh) {
	    if (gh->last_db_article == 0) continue;
	    if (gh->last_db_article < gh->first_db_article) continue;
	    if (!regexec(re, gh->group_name)) continue;
	    any++;
	    if (y = l_re_query(pr, gh)) goto ok;
	}

    Loop_Groups_Sorted(gh) {
	if (flag & 1) {
	    if (gh->master_flag & M_IGNORE_GROUP) continue;
	    if (gh->group_flag & G_SEQUENCE) continue;
	}
	if (!regexec(re, gh->group_name)) continue;
	any++;
	if (y = l_re_query(pr, gh)) goto ok;
    }

    err = any ? "No more groups" : "No group";
    if (flag & 2)
	msg("%s matching `%s'", err, name);
    else
	printf("\n\r%s matching `%s'\n\r", err, name);

    gh = NULL;
    
 ok:
    freeobj(re);
    return y < 0 ? NULL : gh;
}

goto_group(command, ah, access_mode)
int command;
article_header *ah;
flag_type access_mode;
{
    register group_header *gh;
    char ans1, *answer, *mask, buffer[FILENAME], fbuffer[FILENAME];
    article_number first;
    memory_marker mem_marker;
    group_header *orig_group;
    int menu_cmd, cmd_key;
    extern int menu(), file_completion();
    extern article_header *get_menu_article();
    extern int get_from_macro;
    extern group_header *jump_to_group;
    article_number o_cur_first = -1;
    group_header *read_mode_group;
    extern int bypass_consolidation;

#define goto_return( cmd ) \
    { menu_cmd = cmd; goto goto_exit; }

    m_startinput();

    gh = orig_group = current_group;

    if (ah != NULL) {	/* always open new level from reading mode */
	read_mode_group = orig_group;
	orig_group = NULL;
    } else
	read_mode_group = NULL;
    
    mask = NULL;
    if (access_mode == 0) access_mode |= ACC_PARSE_VARIABLES;
    
    if (command == K_GOTO_GROUP)
	goto get_group_name;

    if (command == K_ADVANCE_GROUP)
	gh = gh->next_group;
    else
	gh = gh->prev_group;

    for (;;) {
	if (gh == NULL)
	    goto_return(ME_NO_REDRAW);

	if (gh->first_db_article < gh->last_db_article && gh->current_first <= 0) {
	    sprintf(buffer, "%s%s%s) ",
		   (gh->group_flag & G_UNSUBSCRIBED) ? " UNSUB" : "",
		   (gh->group_flag & G_MERGE_HEAD) ? " MERGED" : "",
		   gh->unread_count <= 0 ? " READ" : "" );
	    buffer[0] = buffer[0] == ')' ? NUL : '(';
	    
	    prompt("\1Enter\1 %s %s?  (ABGNPy) ", gh->group_name, buffer);
	    
	    cmd_key = get_c();
	    if (cmd_key & GETC_COMMAND) {
		command = cmd_key & ~GETC_COMMAND;
		if (command == K_REDRAW) goto_return(ME_REDRAW);
	    } else {
		if (cmd_key == 'y') break;
		command = menu_key_map[cmd_key];
		if (command & K_MACRO) command = orig_menu_map[cmd_key];
	    }
	}

	switch (command) {
	 case K_CONTINUE:
	 case K_CONTINUE_NO_MARK:
	    break;

	 case K_ADVANCE_GROUP:
	    gh = gh->next_group;
	    continue;

	 case K_BACK_GROUP:
	    gh = gh->prev_group;
	    continue;

	 case K_GOTO_GROUP:
	    goto get_group_name;

	 case K_PREVIOUS:
	    while (gh = gh->prev_group) {
		if (gh->group_flag & G_MERGE_SUB) continue;
		if (gh->group_flag & G_COUNTED) break;
		if (gh->newsrc_line != gh->newsrc_orig) break;
	    }
	    continue;

	 case K_NEXT_GROUP_NO_UPDATE:
	    while (gh = gh->next_group) {
		if (gh->group_flag & G_MERGE_SUB) continue;
		if (gh->group_flag & G_COUNTED) break;
		if (gh->newsrc_line != gh->newsrc_orig) break;
	    }
	    continue;

	 default:
	    goto_return(ME_NO_REDRAW);
	}
	break;
    }

    if (gh == orig_group) goto_return(ME_NO_REDRAW);

    goto get_first;

 get_group_name:

    if (current_group == NULL) {
	prompt("\1Enter Group or Folder\1 (+./~) ");
	answer = get_s(NONE, NONE, "+./~", group_completion);
    } else {
	prompt("\1Group or Folder\1 (+./~ %%%s=sneN) ",
	       (gh->master_flag & M_AUTO_ARCHIVE) ? "@" : "");
	strcpy(buffer, "++./0123456789~=% ");
	if (gh->master_flag & M_AUTO_ARCHIVE) buffer[0] = '@';
	answer = get_s(NONE, NONE, buffer, group_completion);
    }

    if (answer == NULL) goto_return(ME_NO_REDRAW);

    if ((ans1 = *answer) == NUL || ans1 == SP) {
	if (current_group == NULL) goto_return(ME_NO_REDRAW);
	goto get_first;
    }

    sprintf(buffer, "%c", ans1);

    switch (ans1) {

     case '@':
	if (current_group == NULL ||
	    (current_group->master_flag & M_AUTO_ARCHIVE) == 0)
	    goto_return(ME_NO_REDRAW);
	answer = current_group->archive_file;
	goto get_folder;

     case '%':
	if (current_group == NULL) goto_return(ME_NO_REDRAW);
	if ((current_group->group_flag & G_FOLDER) == 0) {
	    if (!ah) {
#ifdef NNTP
		if (use_nntp) {
		    msg("Can only use G%% in reading mode");
		    goto_return(ME_NO_REDRAW);
		}
#endif
		prompt("\1READ\1");
		if ((ah = get_menu_article()) == NULL)
		    goto_return(ME_NO_REDRAW);
	    }
	    if ((ah->flag & A_DIGEST) == 0) {
#ifdef NNTP
		if (use_nntp) {
		    extern char *nntp_get_filename();
		    answer = nntp_get_filename(ah->a_number, current_group);
		    goto get_folder;
		}
#endif
		*group_file_name = NUL;
		sprintf(fbuffer, "%s%ld", group_path_name, ah->a_number);
		answer = fbuffer;
		goto get_folder;
	    }
	}

	msg("cannot split articles inside a folder or digest");
	goto_return(ME_NO_REDRAW);

     case '.':
     case '~':
	strcat(buffer, "/");
     case '+':
     case '/':
	if (!get_from_macro) {
	    prompt("\1Folder\1 ");
	    answer = get_s(NONE, buffer, NONE, file_completion);
	    if (answer == NULL || answer[0] == NUL) goto_return(ME_NO_REDRAW);
	}
	goto get_folder;
	
     case 'a':
	if (answer[1] != NUL && strcmp(answer, "all")) break;
	if (current_group == NULL) goto_return(ME_NO_REDRAW);
	access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
	first = 0;
	goto more_articles;

     case 'u':
	if (answer[1] != NUL && strcmp(answer, "unread")) break;
	if (current_group == NULL) goto_return(ME_NO_REDRAW);
	access_mode |= ACC_ORIG_NEWSRC;
	first = gh->first_article + 1;
	goto enter_new_level;

     case 'e':
     case 'n':
     case 's':
	if (answer[1] != NUL && answer[1] != '=') break;
	/* fall thru */
     case '=':
	if (current_group == NULL) goto_return(ME_NO_REDRAW);
	goto get_mask;

     case '0':
     case '1':
     case '2':
     case '3':
     case '4':
     case '5':
     case '6':
     case '7':
     case '8':
     case '9':
	if (current_group == NULL) goto_return(ME_NO_REDRAW);
	if (gh->current_first <= gh->first_db_article) {
	    msg("No extra articles");
	    flush_input();
	    goto_return(ME_NO_REDRAW);
	}
	if (!get_from_macro) {
	    prompt("\1Number of extra articles\1 max %ld: ",
		   gh->current_first - gh->first_db_article);
	    sprintf(buffer, "%c", ans1);
	    answer = get_s(NONE, buffer, NONE, NULL_FCT);
	    if (answer == NULL || *answer ==  NUL) goto_return(ME_NO_REDRAW);
	}
	first = gh->current_first - atol(answer);
	goto more_articles;

     default:
	break;
    }

    if ((gh = lookup_regexp(answer, "Goto", 3)) == NULL)
	goto_return(ME_NO_REDRAW);


 get_first:

    m_advinput();

    if (gh->master_flag & M_BLOCKED ||
	gh->last_db_article == 0 ||
	gh->last_db_article < gh->first_db_article) {
	msg("Group %s is %s", gh->group_name,
	    (gh->master_flag & M_BLOCKED) ? "blocked" : "empty");
	goto_return(ME_NO_REDRAW);
    }

    if (gh != orig_group) {
	if (gh == read_mode_group) {
	    o_cur_first = gh->current_first;
	    gh->current_first = 0;
	}
	if (gh->current_first > 0) {
	    msg("Group %s already active", gh->group_name);
	    goto_return(ME_NO_REDRAW);
	}
	if (o_cur_first < 0) o_cur_first = 0;
	gh->current_first = gh->last_db_article + 1;
    }

    ans1 = ah ? 's' : 'a';
    if (gh == read_mode_group) {
	ans1 = 's';
    } else
    if (gh != orig_group) {
	if (gh->unread_count > 0) ans1 = 'j';
    } else {
	if (gh->unread_count > 0 && gh->current_first > entry_first_article)
	    ans1 = 'u';
	else if (gh->current_first <= gh->first_db_article)
	    ans1 = 's'; 	/* no more articles to read */
    }

    prompt("\1Number of%s articles\1 (%sua%ssne)  (%c) ",
	   gh == orig_group ? " extra" : "",
	   (gh->unread_count > 0) ? "j" : "",
	   (gh->master_flag & M_AUTO_ARCHIVE) ? "@" : "",
	   ans1);

    if (novice)
	msg("Use: j)ump u)nread a)ll @)archive s)ubject n)ame e)ither or number");

    answer = get_s(NONE, NONE, " jua@s=ne", NULL_FCT);
    if (answer == NULL) goto_return(ME_NO_REDRAW);
    if (*answer == NUL || *answer == SP) answer = &ans1;

    switch (*answer) {
     case '@':
	if ((gh->master_flag & M_AUTO_ARCHIVE) == 0) {
	    msg("No archive");
	    goto_return(ME_NO_REDRAW);
	}
	answer = gh->archive_file;
	goto get_folder;

     case 'u':
	access_mode |= ACC_ORIG_NEWSRC;
	first = gh->first_article + 1;
	goto enter_new_level;

     case 'j':
	if (gh == orig_group || gh->unread_count <= 0) {
	    msg("Cannot jump - no unread articles");
	    goto_return(ME_NO_REDRAW);
	}
	if (orig_group == NULL) {	/* nn -g */
	    first = -1;
	    goto enter_new_level;
	}
	jump_to_group = gh;
	goto_return(ME_QUIT);

     case 'a':
	first = 0;
	access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
	goto more_articles;

     case '=':
     case 's':
     case 'n':
     case 'e':
	goto get_mask;

     case '0':
     case '1':
     case '2':
     case '3':
     case '4':
     case '5':
     case '6':
     case '7':
     case '8':
     case '9':
	first = gh->current_first - atol(answer);
	goto more_articles;

     default:
	ding();
	goto_return(ME_NO_REDRAW);
    }


 more_articles:
    if (first > gh->last_db_article) goto_return(ME_NO_REDRAW);

    if (gh != orig_group) goto enter_new_level;

    if (!only_unread_articles && gh->current_first <= gh->first_db_article) {
	msg("No extra articles");
	goto_return(ME_NO_REDRAW);
    }

    if (first < gh->first_db_article) first = gh->first_db_article;

    if (access_group(gh, first, only_unread_articles ?
		     gh->last_db_article : gh->current_first - 1,
		     ACC_PARSE_VARIABLES |
		     ACC_EXTRA_ARTICLES | ACC_ALSO_CROSS_POSTINGS |
		     ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES,
		     (char *)NULL) < 0) {
	msg("Cannot read extra articles (now)");
	goto_return(ME_NO_REDRAW);
    }

    only_unread_articles = 0;
    gh->current_first = first;
    goto_return(ME_REDRAW);
    	
 get_folder:
    m_endinput();
    if (strcmp(answer, "+") == 0)
	answer = (gh && gh->save_file != NULL) ? gh->save_file : 
	         (gh && gh->group_flag & G_FOLDER) ? folder_save_file :
		 default_save_file;
    menu_cmd = folder_menu(answer, 0);
    gh = NULL;
    goto goto_exit;

 get_mask:
    first = 0;
    access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
    switch (*answer) {
     case '=':
	*answer = 's';
     case 's':
	access_mode |= ACC_ON_SUBJECT;
	break;
     case 'n':
	access_mode |= ACC_ON_SENDER;
	break;
     case 'e':
	access_mode |= ACC_ON_SUBJECT | ACC_ON_SENDER;
	break;
    }
    
    if (get_from_macro || answer[1] == '=') {
	mask = answer + 1;
	if (*mask == '=') mask++;
    } else {
	prompt("%c=", *answer);
	mask = get_s(ah == NULL ? NONE :
		     (access_mode & ACC_ON_SUBJECT ? ah->subject : ah->sender),
		     NONE, ah ? NONE : "%=", NULL_FCT);
	if (mask == NULL) goto_return(ME_NO_REDRAW);
	if (*mask == '%' || *mask == '=') {
	    if ((ah = get_menu_article()) == 0) goto_return(ME_NO_REDRAW);
	    *mask = NUL;
	}
    }

    if (*mask == NUL) {
	if (ah == NULL) goto_return(ME_NO_REDRAW);
	strncpy(mask, (access_mode & ACC_ON_SUBJECT) ? ah->subject : ah->sender, GET_S_BUFFER);
	mask[GET_S_BUFFER-1] = NUL;
	bypass_consolidation = 1;
    }

    if (*mask) {
	if (case_fold_search) fold_string(mask);
    } else
	mask = NULL;

 enter_new_level:
    mark_memory(&mem_marker);
    m_endinput();
    if (o_cur_first < 0) o_cur_first = gh->current_first;
    menu_cmd = group_menu(gh, first, access_mode, mask, menu);
    bypass_consolidation = 0;	/* in case no articles were found */
    release_memory(&mem_marker);

 goto_exit:
    if (gh && o_cur_first >= 0) gh->current_first = o_cur_first;

    if (read_mode_group)
	orig_group = read_mode_group;

    if (gh != orig_group) {
	if (orig_group) init_group(orig_group);
    }

    m_endinput();
    return menu_cmd;
}

static merged_header()
{
    so_printxy(0, 0, "MERGED NEWS GROUPS:  %d ARTICLES", n_articles);
    clrline();

    return 1;
}

merge_and_read(access_mode, mask)
flag_type access_mode;
char *mask;
{
    register group_header *gh;
    group_header dummy_group;
    time_t t1, t2;
    time_t trefr = 0;
    long kb = 0, kbleft = 0;

    time(&t1); t2 = 0;

    free_memory();

    access_mode |= ACC_DONT_SORT_ARTICLES | ACC_MERGED_MENU;
    if (!seq_cross_filtering)
	if (also_read_articles || mask || also_cross_postings)
	    access_mode |= ACC_ALSO_CROSS_POSTINGS;
    if (seq_cross_filtering && also_unsub_groups)
	access_mode |= ACC_ALSO_UNSUB_GROUPS;
    if (dont_split_digests)
	access_mode |= ACC_DONT_SPLIT_DIGESTS;

    Loop_Groups_Sequence(gh) {
	if (gh->group_flag & G_FOLDER) continue;
	if (gh->master_flag & M_NO_DIRECTORY) continue;
	if ((gh->group_flag & G_UNSUBSCRIBED) && !also_unsub_groups)
	    continue;
	if (!also_read_articles && gh->last_article >= gh->last_db_article)
	    continue;

	kbleft += gh->data_write_offset;
    }

    Loop_Groups_Sequence(gh) {
	if (s_hangup || s_keyboard) break;

	if (gh->group_flag & G_FOLDER) {
	    printf("\n\rIgnoring folder: %s\n\r", gh->group_name);
	    continue;
	}

	if (gh->master_flag & M_NO_DIRECTORY) continue;
	if ((gh->group_flag & G_UNSUBSCRIBED) && !also_unsub_groups)
	    continue;

	if (also_read_articles)
	    access_mode |= ACC_ALSO_READ_ARTICLES;
	else
	    if (gh->last_article >= gh->last_db_article)
		continue;

	if (init_group(gh) <= 0) continue;

	if (t2 >= trefr) {
	    trefr = t2 + merge_report_rate;

	    if (t2 > 2 && kb > 50000 && kb >= t2)
		printf("\r%4lds\t%lds\t%s", kbleft/(kb/t2),
		       (long)t2, gh->group_name);
	    else
		printf("\r\t%lds\t%s", (long)t2, gh->group_name);

	    clrline();
	}

	access_group(gh, (article_number)(-1), gh->last_db_article,
		     access_mode, mask);

	time(&t2); t2 -= t1;
	kb += gh->data_write_offset;
	kbleft -= gh->data_write_offset;
    }
    merge_memory();
    if (n_articles == 0) return;
    if (dont_sort_articles)
	no_sort_articles();
    else
	sort_articles(-1);

    dummy_group.group_flag = G_FAKED;
    dummy_group.master_flag = 0;
    dummy_group.save_file = NULL;
    dummy_group.group_name = "dummy";
    dummy_group.kill_list = NULL;

    current_group = &dummy_group;

    kb = (kb + 1023) >> 10;
    sprintf(delayed_msg, "Read %ld articles in %ld seconds (%ld kbyte/s)",
	    (long)db_read_counter, (long)t2, t2 > 0 ? kb/t2 : kb);

    menu(merged_header);

    free_memory();
}

unsubscribe(gh)
group_header *gh;
{
    if (no_update) {
	msg("nn started in \"no update\" mode");
	return 0;
    }

    if (gh->group_flag & G_FOLDER) {
	msg("cannot unsubscribe to a folder");
	return 0;
    }

    if (gh->group_flag & G_UNSUBSCRIBED) {
	prompt("Already unsubscribed.  \1Resubscribe to\1 %s ? ",
	       gh->group_name);
	if (yes(0) <= 0) return 0;

	add_to_newsrc(gh);
	add_unread(gh, 1);
    } else {
	prompt("\1Unsubscribe to\1 %s ? ", gh->group_name);
	if (yes(0) <= 0) return 0;

	add_unread(gh, -1);
	update_rc_all(gh, 1);
    }

    return 1;
}

static disp_group(gh)
group_header *gh;
{
    if (pg_next() < 0) return -1;

    printf("%c%6ld%c%s%s%s",
	   (gh->group_flag & G_MERGED) == 0 ? ' ' :
	   (gh->group_flag & G_MERGE_HEAD) ? '&' : '+',

	   (long)(gh->unread_count),

	   (gh == current_group) ? '*' : ' ',

	   gh->group_name,

	   (gh->group_flag & G_NEW) ? " NEW" :
	   (gh->group_flag & G_UNSUBSCRIBED) ? " (!)" : "",

	   gh->enter_macro ? " %" : "");

    return 0;
}

/*
 * amount interpretation:
 *	-1	presentation sequence, unread,subscribed+current
 * 	0	unread,subscribed
 *	1	unread,all
 *	2	all,all 3=>unsub
 */

group_overview(amount)
int amount;
{
    register group_header *gh;

    clrdisp();

    pg_init(0, 2);

    if (amount < 0) {
	Loop_Groups_Sequence(gh) {
	    if (gh->group_flag & G_FAKED) continue;
	    if (gh->master_flag & M_NO_DIRECTORY) continue;
	    if (gh != current_group) {
		if (gh->unread_count <= 0) continue;
		if (gh->group_flag & G_UNSUBSCRIBED && !also_unsub_groups)
		    continue;
	    }
	    if (disp_group(gh) < 0) break;
	}
    } else
    Loop_Groups_Sorted(gh) {
	if (gh->master_flag & (M_NO_DIRECTORY | M_IGNORE_GROUP)) continue;
	if (amount <= 1 && gh->unread_count <= 0) continue;
	if (amount == 0 && (gh->group_flag & G_UNSUBSCRIBED)) continue;
	if (amount == 3 && (gh->group_flag & G_UNSUBSCRIBED) == 0) continue;
	if (amount == 4 && (gh->group_flag & G_SEQUENCE) == 0) continue;

	if (disp_group(gh) < 0) break;
    }

    pg_end();
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.