1 /*
2  *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
3  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
4  *
5  *	News group access.
6  */
7 
8 #include <stdlib.h>
9 #include <sys/types.h>
10 #include <time.h>
11 #include <string.h>
12 #include "config.h"
13 #include "global.h"
14 #include "articles.h"
15 #include "db.h"
16 #include "folder.h"
17 #include "macro.h"
18 #include "match.h"
19 #include "menu.h"
20 #include "newsrc.h"
21 #include "nn.h"
22 #include "nntp.h"
23 #include "regexp.h"
24 #include "sort.h"
25 #include "nn_term.h"
26 #include "variable.h"
27 
28 #ifdef HAVE_SYSLOG
29 #include <syslog.h>
30 #endif
31 
32 /* group.c */
33 
34 static int      print_header(void);
35 static int      l_re_query(char *pr, group_header * gh);
36 static int      merged_header(void);
37 static int      disp_group(group_header * gh);
38 
39 
40 
41 int             dont_split_digests = 0;
42 int             dont_sort_articles = 0;
43 int             also_cross_postings = 0;
44 int             also_unsub_groups = 0;
45 int             entry_message_limit = 400;
46 int             merge_report_rate = 1;
47 
48 extern int      article_limit, also_read_articles;
49 extern int      no_update, novice, case_fold_search;
50 extern int      merged_menu, keep_unsubscribed, keep_unsub_long;
51 extern int      killed_articles;
52 extern int      seq_cross_filtering;
53 extern char    *default_save_file, *folder_save_file;
54 
55 extern char     delayed_msg[];
56 extern int32    db_read_counter;
57 
58 extern long     unread_articles;
59 extern int      unread_groups;
60 extern int      rc_merged_groups_hack;
61 extern int      get_from_macro;
62 extern group_header *jump_to_group;
63 extern int      bypass_consolidation;
64 
65 /*
66  * completion of group name
67  */
68 
69 int
group_completion(char * hbuf,int ix)70 group_completion(char *hbuf, int ix)
71 {
72     static group_number next_group, n1, n2;
73     static char    *head, *tail, *last;
74     static int      tail_offset, prev_lgt, l1, l2;
75     static group_header *prev_group, *p1, *p2;
76     register group_header *gh;
77     register char  *t1, *t2;
78     int             order;
79 
80     if (ix < 0)
81 	return 0;
82 
83     if (hbuf) {
84 	n2 = next_group = 0;
85 	p2 = prev_group = NULL;
86 	l2 = 0;
87 
88 	if ((head = strrchr(hbuf, ',')))
89 	    head++;
90 	else
91 	    head = hbuf;
92 	tail = hbuf + ix;
93 	tail_offset = ix - (head - hbuf);
94 	if ((last = strrchr(head, '.')))
95 	    last++;
96 	else
97 	    last = head;
98 	return 1;
99     }
100     if (ix) {
101 	n1 = next_group, p1 = prev_group, l1 = prev_lgt;
102 	next_group = n2, prev_group = p2, prev_lgt = l2;
103 	list_completion((char *) NULL);
104     }
105     *tail = NUL;
106 
107     while (next_group < master.number_of_groups) {
108 	gh = sorted_groups[next_group++];
109 	if (gh->master_flag & M_IGNORE_GROUP)
110 	    continue;
111 	if (gh->group_name_length <= tail_offset)
112 	    continue;
113 
114 	if (prev_group &&
115 	    strncmp(prev_group->group_name, gh->group_name, prev_lgt) == 0)
116 	    continue;
117 
118 	order = strncmp(gh->group_name, head, tail_offset);
119 	if (order < 0)
120 	    continue;
121 	if (order > 0)
122 	    break;
123 
124 	t1 = gh->group_name + tail_offset;
125 	if ((t2 = strchr(t1, '.'))) {
126 	    strncpy(tail, t1, t2 - t1 + 1);
127 	    tail[t2 - t1 + 1] = NUL;
128 	} else
129 	    strcpy(tail, t1);
130 
131 	prev_group = gh;
132 	prev_lgt = tail_offset + strlen(tail);
133 	if (ix) {
134 	    if (list_completion(last) == 0)
135 		break;
136 	} else
137 	    return 1;
138     }
139 
140     if (ix) {
141 	n2 = next_group, p2 = prev_group, l2 = prev_lgt;
142 	if (n2 > master.number_of_groups)
143 	    n2 = 0, p2 = NULL, l2 = 0;
144 	next_group = n1, prev_group = p1, prev_lgt = l1;
145 	return 1;
146     }
147     next_group = 0;
148     prev_group = NULL;
149     return 0;
150 }
151 
152 static int      group_level = 0;
153 static article_number entry_first_article;
154 static int      only_unread_articles;	/* menu contains only unread art. */
155 
156 static int
print_header(void)157 print_header(void)
158 {
159     so_printxy(0, 0, "Newsgroup: %s", current_group->group_name);
160 
161     if (current_group->group_flag & G_MERGED)
162 	tprintf("    MERGED");
163 
164     clrline();
165 
166     so_gotoxy(-1, 0, 0);
167 
168     so_printf("Articles: %d", n_articles);
169 
170     if (no_update) {
171 	so_printf(" NO UPDATE");
172     } else {
173 	if ((current_group->group_flag & G_MERGED) == 0 &&
174 	    current_group->current_first > entry_first_article)
175 	    so_printf((killed_articles > 0) ? "(L-%ld,K-%d)" : "(L-%ld)",
176 		      current_group->current_first - entry_first_article,
177 		      killed_articles);
178 	else if (killed_articles > 0)
179 	    so_printf(" (K-%d)", killed_articles);
180 
181 	if (unread_articles > 0) {
182 	    so_printf(" of %ld", unread_articles);
183 	    if (unread_groups > 0)
184 		so_printf("/%d", unread_groups);
185 	}
186 	if (current_group->group_flag & G_UNSUBSCRIBED)
187 	    so_printf(" UNSUB");
188 	else if (current_group->group_flag & G_NEW)
189 	    so_printf(" NEW");
190 
191 	if (current_group->unread_count <= 0)
192 	    so_printf(" READ");
193 
194 	if (group_level > 1)
195 	    so_printf(" *NO*UPDATE*");
196     }
197 
198     so_end();
199 
200     return 1;			/* number of lines in header */
201 }
202 
203 /*
204  * interpretation of first_art:
205  *	>0	Read articles first_art..last_db_article (also read)
206  *	-1	Read unread or read articles accoring to -x and -aN flags
207  *
208  * This function is far to long and uses goto's all over! UGH!
209  */
210 
211 int
group_menu(register group_header * gh,article_number first_art,register flag_type access_mode,char * mask,fct_type menu)212 group_menu(register group_header * gh, article_number first_art, register flag_type access_mode, char *mask, fct_type menu)
213 {
214     register group_header *mg_head;
215     article_number  was_unread;
216     int             status, unread_at_reentry = 0, menu_cmd;
217     int             o_killed, o_only_unread;
218 
219 #ifdef RENUMBER_DANGER
220     article_number  prev_last;
221 #endif
222 
223     article_number  n, o_entry_first;
224     flag_type       entry_access_mode = access_mode;
225 
226 #define	menu_return(cmd) { menu_cmd = (cmd); goto menu_exit; }
227 
228     o_entry_first = entry_first_article;
229     o_only_unread = only_unread_articles;
230     o_killed = killed_articles;
231     mark_var_stack();
232 
233     mg_head = (group_level == 0 && gh->group_flag & G_MERGE_HEAD) ? gh : NULL;
234     group_level++;
235 
236 reenter:
237     for (; gh; gh = gh->merge_with) {
238 	if ((gh->master_flag & M_IGNORE_GROUP) || init_group(gh) <= 0) {
239 	    if (mg_head != NULL)
240 		continue;
241 	    menu_return(ME_NEXT);
242 	}
243 	if (unread_at_reentry)
244 	    restore_unread(gh);
245 
246 	m_invoke(-1);		/* invoke macro */
247 
248 	access_mode = entry_access_mode;
249 	if (access_mode & ACC_PARSE_VARIABLES)
250 	    access_mode |= parse_access_flags();
251 
252 	killed_articles = 0;
253 
254 	/*
255 	 * don't lose (a few) new articles when reentering a read group (the
256 	 * user will expect the menu to be the same, and may overlook the new
257 	 * articles)
258 	 */
259 	if (gh->group_flag & G_READ)
260 	    goto update_unsafe;
261 
262 #ifdef RENUMBER_DANGER
263 	prev_last = gh->last_db_article;
264 #endif
265 
266 	was_unread = add_unread(gh, -1);
267 
268 #ifndef NOV			/* This is bad for NOV. reopens .overview */
269 	switch (update_group(gh)) {
270 	    case -1:
271 		status = -1;
272 		goto access_exception;
273 	    case -3:
274 		return ME_QUIT;
275 	}
276 #endif				/* NOV */
277 
278 #ifdef RENUMBER_DANGER
279 	if (gh->last_db_article < prev_last) {
280 	    /* expire + renumbering */
281 	    flag_type       updflag;
282 
283 	    if ((gh->last_article = gh->first_db_article - 1) < 0)
284 		gh->last_article = 0;
285 	    gh->first_article = gh->last_article;
286 	    updflag = gh->group_flag & G_READ;
287 	    gh->group_flag &= ~G_READ;
288 	    update_rc_all(gh, 0);
289 	    gh->group_flag &= ~G_READ;
290 	    gh->group_flag |= updflag;
291 	}
292 #endif
293 
294 	if (was_unread)
295 	    add_unread(gh, 1);
296 
297 update_unsafe:
298 	if ((gh->current_first = first_art) < 0) {
299 	    if (access_mode & (ACC_ORIG_NEWSRC | ACC_MERGED_NEWSRC))
300 		gh->current_first = gh->first_article + 1;
301 	    else if (access_mode & ACC_ALSO_READ_ARTICLES)
302 		gh->current_first = gh->first_db_article;
303 	    else
304 		gh->current_first = gh->last_article + 1;
305 	}
306 	if (gh->current_first <= 0)
307 	    gh->current_first = 1;
308 	entry_first_article = gh->current_first;
309 	only_unread_articles = (access_mode & ACC_ALSO_READ_ARTICLES) == 0;
310 
311 	if (article_limit > 0) {
312 	    n = gh->last_db_article - article_limit + 1;
313 	    if (gh->current_first < n)
314 		gh->current_first = n;
315 	}
316 	if (mg_head != NULL) {
317 	    access_mode |= ACC_MERGED_MENU | ACC_DONT_SORT_ARTICLES;
318 	    if (mg_head != gh)
319 		access_mode |= ACC_EXTRA_ARTICLES;
320 	}
321 	/* entry_message_limit = 1; DEBUG */
322 	if (entry_message_limit &&
323 	    (n = gh->last_db_article - gh->current_first + 1) >= entry_message_limit) {
324 	    clrdisp();
325 	    tprintf("Reading %s: %ld articles...", gh->group_name, (long) n);
326 	} else
327 	    home();
328 	fl;
329 
330 #ifdef NOV
331 	/* the real work is done here :-) */
332 #endif				/* NOV */
333 
334 	status = access_group(gh, gh->current_first, gh->last_db_article,
335 			      access_mode, mask);
336 
337 #ifndef NOV
338 access_exception:
339 #endif
340 
341 	if (status < 0) {
342 	    if (status == -2) {
343 		clrdisp();
344 		tprintf("Read error on group %s (NFS timeout?)\n\r",
345 			current_group->group_name);
346 		tprintf("Skipping to next group....  (interrupt to quit)\n\r");
347 		if (any_key(0) == K_interrupt)
348 		    nn_exit(0);
349 	    }
350 	    if (status <= -10) {
351 		clrdisp();
352 		msg("DATABASE CORRUPTED FOR GROUP %s", gh->group_name);
353 
354 #ifdef HAVE_SYSLOG
355 		openlog("nn", LOG_CONS, LOG_DAEMON);
356 		syslog(LOG_ALERT, "database corrupted for newsgroup %s.",
357 		       gh->group_name);
358 		closelog();
359 #endif
360 
361 		user_delay(5);
362 	    }
363 	    gh->master_flag |= M_BLOCKED;
364 	    if (mg_head != NULL)
365 		continue;
366 	    menu_return(ME_NEXT);
367 	}
368 	if (mg_head == NULL)
369 	    break;
370     }
371 
372     if (n_articles == 0) {
373 	m_break_entry();
374 	menu_return(ME_NO_ARTICLES);
375     }
376     if (mg_head != NULL) {
377 	if (dont_sort_articles)
378 	    no_sort_articles();
379 	else
380 	    sort_articles(-1);
381 	init_group(mg_head);
382     }
383 samemenu:
384     menu_cmd = CALL(menu) (print_header);
385 
386     if (menu_cmd == ME_REENTER_GROUP) {
387 	if (group_level != 1) {
388 	    strcpy(delayed_msg, "can only unread groups at topmost level");
389 	    goto samemenu;
390 	}
391 	free_memory();
392 	if (mg_head)
393 	    gh = mg_head;
394 	unread_at_reentry = 1;
395 	goto reenter;
396     }
397 menu_exit:
398 
399     n = 1;			/* must unsort articles */
400     if (mg_head != NULL)
401 	gh = mg_head;
402 
403     do {
404 	gh->current_first = 0;
405 	if (gh->master_flag & M_BLOCKED)
406 	    continue;
407 	if (mask != NULL)
408 	    continue;
409 	if (access_mode & ACC_ALSO_READ_ARTICLES || first_art >= 0)
410 	    continue;
411 	if (menu_cmd != ME_NO_ARTICLES)
412 	    gh->group_flag &= ~G_NEW;
413 	if (gh->group_flag & G_UNSUBSCRIBED && !keep_unsub_long)
414 	    continue;
415 	if (n)
416 	    sort_articles(-2);
417 	n = 0;
418 	update_rc(gh);
419 	rc_merged_groups_hack = 1;
420     } while (mg_head != NULL && (gh = gh->merge_with) != NULL);
421 
422     rc_merged_groups_hack = 0;
423 
424     killed_articles = o_killed;
425     entry_first_article = o_entry_first;
426     only_unread_articles = o_only_unread;
427     restore_variables();
428     group_level--;
429 
430     return menu_cmd;
431 }
432 
433 static int
l_re_query(char * pr,group_header * gh)434 l_re_query(char *pr, group_header * gh)
435 {
436     if (pr == NULL)
437 	return 1;
438 
439     prompt("\1%s\1 %s ? ", pr, gh->group_name);
440     return yes(0);
441 }
442 
443 group_header   *
lookup_regexp(char * name,char * pr,int flag)444 lookup_regexp(char *name, char *pr, int flag)
445  /* flag		1=>seq order, 2=>msg(err) */
446 {
447     group_header   *gh;
448     regexp         *re;
449     int             y, any;
450     char           *err;
451 
452     if ((gh = lookup(name)))
453 	return gh;
454 
455     if ((re = regcomp(name)) == NULL)
456 	return NULL;
457     y = any = 0;
458 
459     if (pr && (flag & 1))
460 	m_advinput();
461 
462     if (flag & 1)
463 	Loop_Groups_Sequence(gh) {
464 	if (gh->last_db_article == 0)
465 	    continue;
466 	if (gh->last_db_article < gh->first_db_article)
467 	    continue;
468 	if (!regexec(re, gh->group_name))
469 	    continue;
470 	any++;
471 	if ((y = l_re_query(pr, gh)))
472 	    goto ok;
473 	}
474 
475     Loop_Groups_Sorted(gh) {
476 	if (flag & 1) {
477 	    if (gh->master_flag & M_IGNORE_GROUP)
478 		continue;
479 	    if (gh->group_flag & G_SEQUENCE)
480 		continue;
481 	}
482 	if (!regexec(re, gh->group_name))
483 	    continue;
484 	any++;
485 	if ((y = l_re_query(pr, gh)))
486 	    goto ok;
487     }
488 
489     err = any ? "No more groups" : "No group";
490     if (flag & 2)
491 	msg("%s matching `%s'", err, name);
492     else
493 	tprintf("\n\r%s matching `%s'\n\r", err, name);
494 
495     gh = NULL;
496 
497 ok:
498     freeobj(re);
499     return y < 0 ? NULL : gh;
500 }
501 
502 int
goto_group(int command,article_header * ah,flag_type access_mode)503 goto_group(int command, article_header * ah, flag_type access_mode)
504 {
505     register group_header *gh;
506     char            ans1, *answer, *mask, buffer[FILENAME], fbuffer[FILENAME];
507     article_number  first;
508     memory_marker   mem_marker;
509     group_header   *orig_group;
510     int             menu_cmd, cmd_key;
511     article_number  o_cur_first = -1;
512     group_header   *read_mode_group;
513 
514 #define goto_return( cmd ) \
515     { menu_cmd = cmd; goto goto_exit; }
516 
517     m_startinput();
518 
519     gh = orig_group = current_group;
520 
521     if (ah != NULL) {		/* always open new level from reading mode */
522 	read_mode_group = orig_group;
523 	orig_group = NULL;
524     } else
525 	read_mode_group = NULL;
526 
527     mask = NULL;
528     if (access_mode == 0)
529 	access_mode |= ACC_PARSE_VARIABLES;
530 
531     if (command == K_GOTO_GROUP)
532 	goto get_group_name;
533 
534     if (command == K_ADVANCE_GROUP)
535 	gh = gh->next_group;
536     else
537 	gh = gh->prev_group;
538 
539     for (;;) {
540 	if (gh == NULL)
541 	    goto_return(ME_NO_REDRAW);
542 
543 	if (gh->first_db_article < gh->last_db_article && gh->current_first <= 0) {
544 	    sprintf(buffer, "%s%s%s) ",
545 		    (gh->group_flag & G_UNSUBSCRIBED) ? " UNSUB" : "",
546 		    (gh->group_flag & G_MERGE_HEAD) ? " MERGED" : "",
547 		    gh->unread_count <= 0 ? " READ" : "");
548 	    buffer[0] = buffer[0] == ')' ? NUL : '(';
549 
550 	    prompt("\1Enter\1 %s %s?  (ABGNPy) ", gh->group_name, buffer);
551 
552 	    cmd_key = get_c();
553 	    if (cmd_key & GETC_COMMAND) {
554 		command = cmd_key & ~GETC_COMMAND;
555 		if (command == K_REDRAW)
556 		    goto_return(ME_REDRAW);
557 	    } else {
558 		if (cmd_key == 'y')
559 		    break;
560 		command = menu_key_map[cmd_key];
561 		if (command & K_MACRO)
562 		    command = orig_menu_map[cmd_key];
563 	    }
564 	}
565 	switch (command) {
566 	    case K_CONTINUE:
567 	    case K_CONTINUE_NO_MARK:
568 		break;
569 
570 	    case K_ADVANCE_GROUP:
571 		gh = gh->next_group;
572 		continue;
573 
574 	    case K_BACK_GROUP:
575 		gh = gh->prev_group;
576 		continue;
577 
578 	    case K_GOTO_GROUP:
579 		goto get_group_name;
580 
581 	    case K_PREVIOUS:
582 		while (gh->prev_group) {
583 		    gh = gh->prev_group;
584 		    if (gh->group_flag & G_MERGE_SUB)
585 			continue;
586 		    if (gh->group_flag & G_COUNTED)
587 			break;
588 		    if (gh->newsrc_line != gh->newsrc_orig)
589 			break;
590 		}
591 		continue;
592 
593 	    case K_NEXT_GROUP_NO_UPDATE:
594 		while (gh->next_group) {
595 		    gh = gh->next_group;
596 		    if (gh->group_flag & G_MERGE_SUB)
597 			continue;
598 		    if (gh->group_flag & G_COUNTED)
599 			break;
600 		    if (gh->newsrc_line != gh->newsrc_orig)
601 			break;
602 		}
603 		continue;
604 
605 	    default:
606 		goto_return(ME_NO_REDRAW);
607 	}
608 	break;
609     }
610 
611     if (gh == orig_group)
612 	goto_return(ME_NO_REDRAW);
613 
614     goto get_first;
615 
616 get_group_name:
617 
618     if (current_group == NULL) {
619 	prompt("\1Enter Group or Folder\1 (+./~) ");
620 	answer = get_s(NONE, NONE, "+./~", group_completion);
621     } else {
622 
623 #ifndef ART_GREP
624 	prompt("\1Group or Folder\1 (+./~ %%%s=sneN) ",
625 #else
626 	prompt("\1Group or Folder\1 (+./~ %%%s=sneNbB) ",	/* add 'bB' for BODY */
627 #endif				/* ART_GREP */
628 
629 	       (gh->master_flag & M_AUTO_ARCHIVE) ? "@" : "");
630 	strcpy(buffer, "++./0123456789~=% ");
631 	if (gh->master_flag & M_AUTO_ARCHIVE)
632 	    buffer[0] = '@';
633 	answer = get_s(NONE, NONE, buffer, group_completion);
634     }
635 
636     if (answer == NULL)
637 	goto_return(ME_NO_REDRAW);
638 
639     if ((ans1 = *answer) == NUL || ans1 == SP) {
640 	if (current_group == NULL)
641 	    goto_return(ME_NO_REDRAW);
642 	goto get_first;
643     }
644     sprintf(buffer, "%c", ans1);
645 
646     switch (ans1) {
647 
648 	case '@':
649 	    if (current_group == NULL ||
650 		(current_group->master_flag & M_AUTO_ARCHIVE) == 0)
651 		goto_return(ME_NO_REDRAW);
652 	    answer = current_group->archive_file;
653 	    goto get_folder;
654 
655 	case '%':
656 	    if (current_group == NULL)
657 		goto_return(ME_NO_REDRAW);
658 	    if ((current_group->group_flag & G_FOLDER) == 0) {
659 		if (!ah) {
660 
661 #ifdef NNTP
662 		    if (use_nntp) {
663 			msg("Can only use G%% in reading mode");
664 			goto_return(ME_NO_REDRAW);
665 		    }
666 #endif
667 
668 		    prompt("\1READ\1");
669 		    if ((ah = get_menu_article()) == NULL)
670 			goto_return(ME_NO_REDRAW);
671 		}
672 		if ((ah->flag & A_DIGEST) == 0) {
673 
674 #ifdef NNTP
675 		    if (use_nntp) {
676 			answer = nntp_get_filename(ah->a_number, current_group);
677 			goto get_folder;
678 		    }
679 #endif
680 
681 		    *group_file_name = NUL;
682 		    sprintf(fbuffer, "%s%ld", group_path_name, ah->a_number);
683 		    answer = fbuffer;
684 		    goto get_folder;
685 		}
686 	    }
687 	    msg("cannot split articles inside a folder or digest");
688 	    goto_return(ME_NO_REDRAW);
689 
690 	    /* FALLTHROUGH */
691 	case '.':
692 	case '~':
693 	    strcat(buffer, "/");
694 	    /* FALLTHROUGH */
695 	case '+':
696 	case '/':
697 	    if (!get_from_macro) {
698 		prompt("\1Folder\1 ");
699 		answer = get_s(NONE, buffer, NONE, file_completion);
700 		if (answer == NULL || answer[0] == NUL)
701 		    goto_return(ME_NO_REDRAW);
702 	    }
703 	    goto get_folder;
704 
705 	case 'a':
706 	    if (answer[1] != NUL && strcmp(answer, "all"))
707 		break;
708 	    if (current_group == NULL)
709 		goto_return(ME_NO_REDRAW);
710 	    access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
711 	    first = 0;
712 	    goto more_articles;
713 
714 	case 'u':
715 	    if (answer[1] != NUL && strcmp(answer, "unread"))
716 		break;
717 	    if (current_group == NULL)
718 		goto_return(ME_NO_REDRAW);
719 	    access_mode |= ACC_ORIG_NEWSRC;
720 	    first = gh->first_article + 1;
721 	    goto enter_new_level;
722 
723 #ifdef ART_GREP
724 	case 'B':		/* Body search all articles in database */
725 	case 'b':		/* Body search unread articles */
726 	    if (answer[1] != NUL && answer[1] != '=')
727 		break;
728 	    if (current_group == NULL)
729 		goto_return(ME_NO_REDRAW);
730 	    access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
731 	    goto get_mask;
732 #endif				/* ART_GREP */
733 
734 	case 'e':
735 	case 'n':
736 	case 's':
737 	    if (answer[1] != NUL && answer[1] != '=')
738 		break;
739 	    /* FALLTHROUGH */
740 	case '=':
741 	    if (current_group == NULL)
742 		goto_return(ME_NO_REDRAW);
743 	    goto get_mask;
744 
745 	case '0':
746 	case '1':
747 	case '2':
748 	case '3':
749 	case '4':
750 	case '5':
751 	case '6':
752 	case '7':
753 	case '8':
754 	case '9':
755 	    if (current_group == NULL)
756 		goto_return(ME_NO_REDRAW);
757 	    if (gh->current_first <= gh->first_db_article) {
758 		msg("No extra articles");
759 		flush_input();
760 		goto_return(ME_NO_REDRAW);
761 	    }
762 	    if (!get_from_macro) {
763 		prompt("\1Number of extra articles\1 max %ld: ",
764 		       gh->current_first - gh->first_db_article);
765 		sprintf(buffer, "%c", ans1);
766 		answer = get_s(NONE, buffer, NONE, NULL_FCT);
767 		if (answer == NULL || *answer == NUL)
768 		    goto_return(ME_NO_REDRAW);
769 	    }
770 	    first = gh->current_first - (article_number) atol(answer);
771 	    goto more_articles;
772 
773 	default:
774 	    break;
775     }
776 
777     if ((gh = lookup_regexp(answer, "Goto", 3)) == NULL)
778 	goto_return(ME_NO_REDRAW);
779 
780 
781 get_first:
782 
783     m_advinput();
784 
785     if (gh->master_flag & M_BLOCKED ||
786 	gh->last_db_article == 0 ||
787 	gh->last_db_article < gh->first_db_article) {
788 	msg("Group %s is %s", gh->group_name,
789 	    (gh->master_flag & M_BLOCKED) ? "blocked" : "empty");
790 	goto_return(ME_NO_REDRAW);
791     }
792     if (gh != orig_group) {
793 	if (gh == read_mode_group) {
794 	    o_cur_first = gh->current_first;
795 	    gh->current_first = 0;
796 	}
797 	if (gh->current_first > 0) {
798 	    msg("Group %s already active", gh->group_name);
799 	    goto_return(ME_NO_REDRAW);
800 	}
801 	if (o_cur_first < 0)
802 	    o_cur_first = 0;
803 	gh->current_first = gh->last_db_article + 1;
804     }
805     ans1 = ah ? 's' : 'a';
806     if (gh == read_mode_group) {
807 	ans1 = 's';
808     } else if (gh != orig_group) {
809 	if (gh->unread_count > 0)
810 	    ans1 = 'j';
811     } else {
812 	if (gh->unread_count > 0 && gh->current_first > entry_first_article)
813 	    ans1 = 'u';
814 	else if (gh->current_first <= gh->first_db_article)
815 	    ans1 = 's';		/* no more articles to read */
816     }
817 
818     prompt("\1Number of%s articles\1 (%sua%ssne)  (%c) ",
819 	   gh == orig_group ? " extra" : "",
820 	   (gh->unread_count > 0) ? "j" : "",
821 	   (gh->master_flag & M_AUTO_ARCHIVE) ? "@" : "",
822 	   ans1);
823 
824     if (novice)
825 	msg("Use: j)ump u)nread a)ll @)archive s)ubject n)ame e)ither or number");
826 
827 #ifndef ART_GREP
828     answer = get_s(NONE, NONE, " jua@s=ne", NULL_FCT);
829 #else
830     answer = get_s(NONE, NONE, " jua@s=nebB", NULL_FCT);	/* add 'bB' for BODY */
831 #endif				/* ART_GREP */
832 
833     if (answer == NULL)
834 	goto_return(ME_NO_REDRAW);
835     if (*answer == NUL || *answer == SP)
836 	answer = &ans1;
837 
838     switch (*answer) {
839 	case '@':
840 	    if ((gh->master_flag & M_AUTO_ARCHIVE) == 0) {
841 		msg("No archive");
842 		goto_return(ME_NO_REDRAW);
843 	    }
844 	    answer = gh->archive_file;
845 	    goto get_folder;
846 
847 	case 'u':
848 	    access_mode |= ACC_ORIG_NEWSRC;
849 	    first = gh->first_article + 1;
850 	    goto enter_new_level;
851 
852 	case 'j':
853 	    if (gh == orig_group || gh->unread_count <= 0) {
854 		msg("Cannot jump - no unread articles");
855 		goto_return(ME_NO_REDRAW);
856 	    }
857 	    if (orig_group == NULL) {	/* nn -g */
858 		first = -1;
859 		goto enter_new_level;
860 	    }
861 	    jump_to_group = gh;
862 	    goto_return(ME_QUIT);
863 	    break;		/* for lint */
864 
865 	case 'a':
866 	    first = 0;
867 	    access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
868 	    goto more_articles;
869 
870 	case '=':
871 	case 's':
872 	case 'n':
873 	case 'e':
874 	    goto get_mask;
875 
876 #ifdef ART_GREP
877 	case 'b':		/* Body search unread articles */
878 	case 'B':		/* Body search all articles */
879 	    access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
880 	    goto get_mask;
881 #endif				/* ART_GREP */
882 
883 	case '0':
884 	case '1':
885 	case '2':
886 	case '3':
887 	case '4':
888 	case '5':
889 	case '6':
890 	case '7':
891 	case '8':
892 	case '9':
893 	    first = gh->current_first - atol(answer);
894 	    goto more_articles;
895 
896 	default:
897 	    ding();
898 	    goto_return(ME_NO_REDRAW);
899     }
900 
901 
902 more_articles:
903     if (first > gh->last_db_article)
904 	goto_return(ME_NO_REDRAW);
905 
906     if (gh != orig_group)
907 	goto enter_new_level;
908 
909     if (!only_unread_articles)
910 
911 #ifdef NOV
912 	if (gh->current_first <= gh->first_a_article)
913 #else
914 	if (gh->current_first <= gh->first_db_article)
915 #endif				/* NOV */
916 
917 	{
918 	    msg("No extra articles");
919 	    goto_return(ME_NO_REDRAW);
920 	}
921 
922 #ifdef NOV
923     if (first < gh->first_a_article)
924 	first = gh->first_a_article;
925 #else
926     if (first < gh->first_db_article)
927 	first = gh->first_db_article;
928 #endif				/* NOV */
929 
930     if (access_group(gh, first, only_unread_articles ?
931 		     gh->last_db_article : gh->current_first - 1,
932 		     ACC_PARSE_VARIABLES |
933 		     ACC_EXTRA_ARTICLES | ACC_ALSO_CROSS_POSTINGS |
934 		     ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES,
935 		     (char *) NULL) < 0) {
936 	msg("Cannot read extra articles (now)");
937 	goto_return(ME_NO_REDRAW);
938     }
939     only_unread_articles = 0;
940     gh->current_first = first;
941     goto_return(ME_REDRAW);
942 
943 get_folder:
944     m_endinput();
945     if (strcmp(answer, "+") == 0)
946 	answer = (gh && gh->save_file != NULL) ? gh->save_file :
947 	    (gh && gh->group_flag & G_FOLDER) ? folder_save_file :
948 	    default_save_file;
949     menu_cmd = folder_menu(answer, 0);
950     gh = NULL;
951     goto goto_exit;
952 
953 get_mask:
954     first = 0;
955     access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
956     switch (*answer) {
957 	case '=':
958 	    *answer = 's';
959 	    /* FALLTHROUGH */
960 	case 's':
961 	    access_mode |= ACC_ON_SUBJECT;
962 	    break;
963 	case 'n':
964 	    access_mode |= ACC_ON_SENDER;
965 	    break;
966 	case 'e':
967 	    access_mode |= ACC_ON_SUBJECT | ACC_ON_SENDER;
968 	    break;
969 
970 #ifdef ART_GREP
971 	case 'b':		/* Body search unread articles */
972 	    access_mode |= ACC_ON_GREP_UNREAD;
973 	    break;
974 	case 'B':		/* Body search all articles in database */
975 	    access_mode |= ACC_ON_GREP_ALL;
976 	    break;
977 #endif				/* ART_GREP */
978     }
979 
980 #ifdef ART_GREP
981     if ((*answer == 'b') || (*answer == 'B')) {
982 	prompt("Article body search pattern=");
983 	mask = get_s(NONE, NONE, NONE, NULL_FCT);
984 	if (mask == NULL)
985 	    goto_return(ME_NO_REDRAW);
986     } else
987 #endif
988 
989     if (get_from_macro || answer[1] == '=') {
990 	mask = answer + 1;
991 	if (*mask == '=')
992 	    mask++;
993     } else {
994 	prompt("%c=", *answer);
995 	mask = get_s(ah == NULL ? NONE :
996 		  (access_mode & ACC_ON_SUBJECT ? ah->subject : ah->sender),
997 		     NONE, ah ? NONE : "%=", NULL_FCT);
998 	if (mask == NULL)
999 	    goto_return(ME_NO_REDRAW);
1000 	if (*mask == '%' || *mask == '=') {
1001 	    if ((ah = get_menu_article()) == 0)
1002 		goto_return(ME_NO_REDRAW);
1003 	    *mask = NUL;
1004 	}
1005     }
1006 
1007     if (*mask == NUL) {
1008 	if (ah == NULL)
1009 	    goto_return(ME_NO_REDRAW);
1010 	strncpy(mask, (access_mode & ACC_ON_SUBJECT) ? ah->subject : ah->sender, GET_S_BUFFER);
1011 	mask[GET_S_BUFFER - 1] = NUL;
1012 	bypass_consolidation = 1;
1013     }
1014     if (*mask) {
1015 	if (case_fold_search)
1016 	    fold_string(mask);
1017     } else
1018 	mask = NULL;
1019 
1020 enter_new_level:
1021     mark_memory(&mem_marker);
1022     m_endinput();
1023     if (o_cur_first < 0)
1024 	o_cur_first = gh->current_first;
1025     menu_cmd = group_menu(gh, first, access_mode, mask, menu);
1026     bypass_consolidation = 0;	/* in case no articles were found */
1027     release_memory(&mem_marker);
1028 
1029 goto_exit:
1030     if (gh && o_cur_first >= 0)
1031 	gh->current_first = o_cur_first;
1032 
1033     if (read_mode_group)
1034 	orig_group = read_mode_group;
1035 
1036     if (gh != orig_group) {
1037 	if (orig_group)
1038 	    init_group(orig_group);
1039     }
1040     m_endinput();
1041     return menu_cmd;
1042 }
1043 
1044 static int
merged_header(void)1045 merged_header(void)
1046 {
1047     so_printxy(0, 0, "MERGED NEWS GROUPS:  %d ARTICLES", n_articles);
1048     clrline();
1049 
1050     return 1;
1051 }
1052 
1053 void
merge_and_read(flag_type access_mode,char * mask)1054 merge_and_read(flag_type access_mode, char *mask)
1055 {
1056     register group_header *gh;
1057     group_header    dummy_group;
1058     time_t          t1, t2;
1059     time_t          trefr = 0;
1060     long            kb = 0, kbleft = 0;
1061 
1062     time(&t1);
1063     t2 = 0;
1064 
1065     free_memory();
1066 
1067     access_mode |= ACC_DONT_SORT_ARTICLES | ACC_MERGED_MENU;
1068     if (!seq_cross_filtering)
1069 	if (also_read_articles || mask || also_cross_postings)
1070 	    access_mode |= ACC_ALSO_CROSS_POSTINGS;
1071     if (seq_cross_filtering && also_unsub_groups)
1072 	access_mode |= ACC_ALSO_UNSUB_GROUPS;
1073     if (dont_split_digests)
1074 	access_mode |= ACC_DONT_SPLIT_DIGESTS;
1075 
1076     Loop_Groups_Sequence(gh) {
1077 	if (gh->group_flag & G_FOLDER)
1078 	    continue;
1079 	if (gh->master_flag & M_NO_DIRECTORY)
1080 	    continue;
1081 	if ((gh->group_flag & G_UNSUBSCRIBED) && !also_unsub_groups)
1082 	    continue;
1083 	if (!also_read_articles && gh->last_article >= gh->last_db_article)
1084 	    continue;
1085 
1086 	kbleft += gh->data_write_offset;
1087     }
1088 
1089     gotoxy(0, Lines - 1);
1090     Loop_Groups_Sequence(gh) {
1091 	if (s_hangup || s_keyboard)
1092 	    break;
1093 
1094 	if (gh->group_flag & G_FOLDER) {
1095 	    tprintf("\n\rIgnoring folder: %s\n\r", gh->group_name);
1096 	    continue;
1097 	}
1098 	if (gh->master_flag & M_NO_DIRECTORY)
1099 	    continue;
1100 	if ((gh->group_flag & G_UNSUBSCRIBED) && !also_unsub_groups)
1101 	    continue;
1102 
1103 	if (also_read_articles)
1104 	    access_mode |= ACC_ALSO_READ_ARTICLES;
1105 	else if (gh->last_article >= gh->last_db_article)
1106 	    continue;
1107 
1108 	if (init_group(gh) <= 0)
1109 	    continue;
1110 
1111 #define SILLY 1			/* print dumb progress report */
1112 
1113 #ifdef SILLY
1114 	if (t2 >= trefr) {
1115 	    trefr = t2 + merge_report_rate;
1116 
1117 	    if (t2 > 2 && kb > 50000 && kb >= t2)
1118 		tprintf("\r%4lds\t%lds\t%s", kbleft / (kb / t2),
1119 			(long) t2, gh->group_name);
1120 	    else
1121 		tprintf("\r\t%lds\t%s", (long) t2, gh->group_name);
1122 
1123 	    clrline();
1124 	}
1125 #else
1126 	{
1127 	    static int      grcnt = 0;
1128 	    if (++grcnt % 10 == 0) {
1129 		tputc('.');
1130 		/* Hmm.. would a  fflush(stdout); be in order here? */
1131 	    }
1132 	}
1133 #endif
1134 
1135 	/* db_read_group(gh); *//* open the database file */
1136 
1137 	access_group(gh, (article_number) (-1), gh->last_db_article,
1138 		     access_mode, mask);
1139 
1140 	time(&t2);
1141 	t2 -= t1;
1142 	kb += gh->data_write_offset;
1143 	kbleft -= gh->data_write_offset;
1144     }
1145     merge_memory();
1146     if (n_articles == 0)
1147 	return;
1148     if (dont_sort_articles)
1149 	no_sort_articles();
1150     else
1151 	sort_articles(-1);
1152 
1153     dummy_group.group_flag = G_FAKED;
1154     dummy_group.master_flag = 0;
1155     dummy_group.save_file = NULL;
1156     dummy_group.group_name = "dummy";
1157     dummy_group.kill_list = NULL;
1158 
1159     current_group = &dummy_group;
1160 
1161     kb = (kb + 1023) >> 10;
1162     sprintf(delayed_msg, "Read %ld articles in %ld seconds (%ld kbyte/s)",
1163 	    (long) db_read_counter, (long) t2, t2 > 0 ? kb / t2 : kb);
1164 
1165     menu(merged_header);
1166 
1167     free_memory();
1168 }
1169 
1170 int
unsubscribe(group_header * gh)1171 unsubscribe(group_header * gh)
1172 {
1173     if (no_update) {
1174 	msg("nn started in \"no update\" mode");
1175 	return 0;
1176     }
1177     if (gh->group_flag & G_FOLDER) {
1178 	msg("cannot unsubscribe to a folder");
1179 	return 0;
1180     }
1181     if (gh->group_flag & G_UNSUBSCRIBED) {
1182 	prompt("Already unsubscribed.  \1Resubscribe to\1 %s ? ",
1183 	       gh->group_name);
1184 	if (yes(0) <= 0)
1185 	    return 0;
1186 
1187 	add_to_newsrc(gh);
1188 	add_unread(gh, 1);
1189     } else {
1190 	prompt("\1Unsubscribe to\1 %s ? ", gh->group_name);
1191 	if (yes(0) <= 0)
1192 	    return 0;
1193 
1194 	add_unread(gh, -1);
1195 	update_rc_all(gh, 1);
1196     }
1197 
1198     return 1;
1199 }
1200 
1201 static int
disp_group(group_header * gh)1202 disp_group(group_header * gh)
1203 {
1204     if (pg_next() < 0)
1205 	return -1;
1206 
1207     tprintf("%c%6ld%c%s%s%s",
1208 	    (gh->group_flag & G_MERGED) == 0 ? ' ' :
1209 	    (gh->group_flag & G_MERGE_HEAD) ? '&' : '+',
1210 
1211 	    (long) (gh->unread_count),
1212 
1213 	    (gh == current_group) ? '*' : ' ',
1214 
1215 	    gh->group_name,
1216 
1217 	    (gh->group_flag & G_NEW) ? " NEW" :
1218 	    (gh->group_flag & G_UNSUBSCRIBED) ? " (!)" : "",
1219 
1220 	    gh->enter_macro ? " %" : "");
1221 
1222     return 0;
1223 }
1224 
1225 /*
1226  * amount interpretation:
1227  *	-1	presentation sequence, unread,subscribed+current
1228  * 	0	unread,subscribed
1229  *	1	unread,all
1230  *	2	all,all 3=>unsub
1231  */
1232 
1233 void
group_overview(int amount)1234 group_overview(int amount)
1235 {
1236     register group_header *gh;
1237 
1238     clrdisp();
1239 
1240     pg_init(0, 2);
1241 
1242     if (amount < 0) {
1243 	Loop_Groups_Sequence(gh) {
1244 	    if (gh->group_flag & G_FAKED)
1245 		continue;
1246 	    if (gh->master_flag & M_NO_DIRECTORY)
1247 		continue;
1248 	    if (gh != current_group) {
1249 		if (gh->unread_count <= 0)
1250 		    continue;
1251 		if (gh->group_flag & G_UNSUBSCRIBED && !also_unsub_groups)
1252 		    continue;
1253 	    }
1254 	    if (disp_group(gh) < 0)
1255 		break;
1256 	}
1257     } else
1258 	Loop_Groups_Sorted(gh) {
1259 	if (gh->master_flag & (M_NO_DIRECTORY | M_IGNORE_GROUP))
1260 	    continue;
1261 	if (amount <= 1 && gh->unread_count <= 0)
1262 	    continue;
1263 	if (amount == 0 && (gh->group_flag & G_UNSUBSCRIBED))
1264 	    continue;
1265 	if (amount == 3 && (gh->group_flag & G_UNSUBSCRIBED) == 0)
1266 	    continue;
1267 	if (amount == 4 && (gh->group_flag & G_SEQUENCE) == 0)
1268 	    continue;
1269 
1270 	if (disp_group(gh) < 0)
1271 	    break;
1272 	}
1273 
1274     pg_end();
1275 }
1276