home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / nn.tar / nn-6.5.1 / group.c < prev    next >
C/C++ Source or Header  |  1996-08-19  |  29KB  |  1,180 lines

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