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