home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume22 / nn6.4 / part10 / group.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-06-07  |  21.9 KB  |  943 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 "term.h"
  11. #include "menu.h"
  12. #include "keymap.h"
  13. #ifdef HAVE_SYSLOG
  14. #include <syslog.h>
  15. #endif
  16.  
  17. export int  dont_split_digests = 0;
  18. export int  dont_sort_articles = 0;
  19. export int  also_cross_postings = 0;
  20. export int  also_unsub_groups = 0;
  21. export int  entry_message_limit = 400;
  22.  
  23. import int  article_limit, also_read_articles;
  24. import int  no_update, novice, case_fold_search;
  25. import int  merged_menu, keep_unsubscribed, keep_unsub_long;
  26. import int  killed_articles;
  27. import int  seq_cross_filtering;
  28. import char *default_save_file;
  29.  
  30. import char delayed_msg[];
  31. import int32 db_read_counter;
  32.  
  33. /*
  34.  * completion of group name
  35.  */
  36.  
  37. group_completion(hbuf, ix)
  38. char *hbuf;
  39. int ix;
  40. {
  41.     static group_number next_group, n1, n2;
  42.     static char *head, *tail, *last;
  43.     static int  tail_offset, prev_lgt, l1, l2;
  44.     static group_header *prev_group, *p1, *p2;
  45.     register group_header *gh;
  46.     register char *t1, *t2;
  47.     int order;
  48.  
  49.     if (ix < 0) return 0;
  50.  
  51.     if (hbuf) {
  52.     n2 = next_group = 0;
  53.     p2 = prev_group = NULL;
  54.     l2 = 0;
  55.  
  56.     if (head = strrchr(hbuf, ','))
  57.         head++;
  58.     else
  59.         head = hbuf;
  60.     tail = hbuf + ix;
  61.     tail_offset = ix - (head - hbuf);
  62.     if (last = strrchr(head, '.')) last++; else last = head;
  63.     return 1;
  64.     }
  65.  
  66.     if (ix) {
  67.     n1 = next_group, p1 = prev_group, l1 = prev_lgt;
  68.     next_group = n2, prev_group = p2, prev_lgt = l2;
  69.     list_completion((char *)NULL);
  70.     }
  71.  
  72.     *tail = NUL;
  73.  
  74.     while (next_group < master.number_of_groups) {
  75.     gh = sorted_groups[next_group++];
  76.     if (gh->master_flag & M_IGNORE_GROUP) continue;
  77.     if (gh->group_name_length <= tail_offset) continue;
  78.  
  79.     if (prev_group &&
  80.         strncmp(prev_group->group_name, gh->group_name, prev_lgt) == 0)
  81.         continue;
  82.  
  83.     order = strncmp(gh->group_name, head, tail_offset);
  84.     if (order < 0) continue;
  85.     if (order > 0) break;
  86.  
  87.     t1 = gh->group_name + tail_offset;
  88.     if (t2 = strchr(t1, '.')) {
  89.         strncpy(tail, t1, t2 - t1 + 1);
  90.         tail[t2 - t1 + 1] = NUL;
  91.     } else
  92.         strcpy(tail, t1);
  93.  
  94.     prev_group = gh;
  95.     prev_lgt = tail_offset + strlen(tail);
  96.     if (ix) {
  97.         if (list_completion(last) == 0) break;
  98.     } else
  99.         return 1;
  100.     }
  101.  
  102.     if (ix) {
  103.     n2 = next_group, p2 = prev_group, l2 = prev_lgt;
  104.     if (n2 > master.number_of_groups)
  105.         n2 = 0, p2 = NULL, l2 = 0;
  106.     next_group = n1, prev_group = p1, prev_lgt = l1;
  107.     return 1;
  108.     }
  109.  
  110.     next_group = 0;
  111.     prev_group = NULL;
  112.     return 0;
  113. }
  114.  
  115. static int group_level = 0;
  116.  
  117. static print_header()
  118. {
  119.     extern long unread_articles;
  120.     extern int unread_groups;
  121.  
  122.     so_printxy(0, 0, "Newsgroup: %s", current_group->group_name);
  123.  
  124.     if (current_group->group_flag & G_MERGED) printf("    MERGED");
  125.  
  126.     clrline();
  127.  
  128.     so_gotoxy(-1, 0, 0);
  129.  
  130.     so_printf("Articles: %d", n_articles);
  131.  
  132.     if (no_update) {
  133.     so_printf(" NO UPDATE");
  134.     } else {
  135.     if (current_group->current_first > current_group->first_article + 1)
  136.         so_printf((killed_articles > 0) ? "(L-%ld,K-%d)" : "(L-%ld)",
  137.               current_group->current_first - current_group->first_article - 1,
  138.               killed_articles);
  139.     else
  140.         if (killed_articles > 0)
  141.         so_printf(" (K-%d)", killed_articles);
  142.  
  143.     if (unread_articles > 0) {
  144.         so_printf(" of %ld", unread_articles);
  145.         if (unread_groups > 0)
  146.         so_printf("/%d", unread_groups);
  147.     }
  148.  
  149.     if (current_group->group_flag & G_UNSUBSCRIBED)
  150.         so_printf(" UNSUB");
  151.     else if (current_group->group_flag & G_NEW)
  152.         so_printf(" NEW");
  153.  
  154.     if (current_group->unread_count <= 0)
  155.         so_printf(" READ");
  156.  
  157.     if (group_level > 1)
  158.         so_printf(" *NO*UPDATE*");
  159.     }
  160.  
  161.     so_end();
  162.  
  163.     return 1;    /* number of lines in header */
  164. }
  165.  
  166. /*
  167.  * interpretation of first_art:
  168.  *    >0    Read articles first_art..last_db_article (also read)
  169.  *    -1    Read unread or read articles accoring to -x and -aN flags
  170.  */
  171.  
  172. group_menu(gh, first_art, access_mode, mask, menu)
  173. register group_header *gh;
  174. article_number first_art;
  175. register flag_type access_mode;
  176. char *mask;
  177. fct_type menu;
  178. {
  179.     int status, was_unread, unread_at_reentry = 0;
  180.     int menu_cmd, o_killed;
  181.     article_number prev_last, n;
  182.     register group_header *mg_head;
  183.  
  184. #define    menu_return(cmd) { menu_cmd = (cmd); goto menu_exit; }
  185.  
  186.     o_killed = killed_articles;
  187.     mark_var_stack();
  188.  
  189.     mg_head = (group_level == 0 && gh->group_flag & G_MERGE_HEAD) ? gh : NULL;
  190.     group_level++;
  191.  
  192.  reenter:
  193.     for (; gh; gh = gh->merge_with) {
  194.     if (init_group(gh) <= 0) {
  195.         if (mg_head != NULL) continue;
  196.         menu_return( ME_NEXT );
  197.     }
  198.  
  199.     if (unread_at_reentry) restore_unread(gh);
  200.  
  201.     m_invoke(-1);
  202.  
  203.     killed_articles = 0;
  204.  
  205.     /* don't lose (a few) new articles when reentering a read group */
  206.     /* (the user will expect the menu to be the same, and may overlook */
  207.     /* the new articles) */
  208.     if (gh->group_flag & G_READ) goto update_unsafe;
  209.  
  210. #ifdef RENUMBER_DANGER
  211.     prev_last = gh->last_db_article;
  212. #endif
  213.     was_unread = add_unread(gh, -1);
  214.  
  215.     switch (update_group(gh)) {
  216.      case -1:
  217.         status = -1;
  218.         goto access_exception;
  219.      case -3:
  220.         return ME_QUIT;
  221.     }
  222.  
  223. #ifdef RENUMBER_DANGER
  224.     if (gh->last_db_article < prev_last) {
  225.         /* expire + renumbering */
  226.         flag_type updflag;
  227.  
  228.         if ((gh->last_article = gh->first_db_article - 1) < 0)
  229.         gh->last_article = 0;
  230.         gh->first_article = gh->last_article;
  231.         updflag = gh->group_flag & G_READ;
  232.         gh->group_flag &= ~G_READ;
  233.         update_rc_all(gh, 0);
  234.         gh->group_flag &= ~G_READ;
  235.         gh->group_flag |= updflag;
  236.     }
  237. #endif
  238.  
  239.     if (was_unread)
  240.         add_unread(gh, 1);
  241.  
  242.      update_unsafe:
  243.     if ((gh->current_first = first_art) < 0) {
  244.         if (access_mode & ACC_ORIG_NEWSRC)
  245.         gh->current_first = gh->first_article + 1;
  246.         else if (access_mode & ACC_ALSO_READ_ARTICLES)
  247.         gh->current_first = gh->first_db_article;
  248.         else
  249.         gh->current_first = gh->last_article + 1;
  250.     }
  251.     
  252.     if (gh->current_first <= 0) gh->current_first = 1;
  253.     
  254.     if (article_limit > 0) {
  255.         n = gh->last_db_article - article_limit + 1;
  256.         if (gh->current_first < n) gh->current_first = n;
  257.     }
  258.  
  259.     if (mg_head != NULL) {
  260.         access_mode |= ACC_MERGED_MENU | ACC_DONT_SORT_ARTICLES;
  261.         if (mg_head != gh) access_mode |= ACC_EXTRA_ARTICLES;
  262.     }
  263.  
  264.     if (entry_message_limit &&
  265.         (n = gh->last_db_article - gh->current_first) >= entry_message_limit) {
  266.         clrdisp();
  267.         printf("Reading %s: %ld articles...", gh->group_name, (long)n);
  268.     } else
  269.         home();
  270.     fl;
  271.  
  272.     status = access_group(gh, gh->current_first, gh->last_db_article,
  273.                   access_mode, mask);
  274.  
  275.      access_exception:
  276.  
  277.     if (status < 0) {
  278.         if (status == -2) {
  279.         clrdisp();
  280.         printf("Read error on group %s (NFS timeout?)\n\r",
  281.                current_group->group_name);
  282.         printf("Skipping to next group....  (interrupt to quit)\n\r");
  283.         if (any_key(0) == K_interrupt) nn_exit(0);
  284.         }
  285.  
  286.         if (status <= -10) {
  287.         clrdisp();
  288.         msg("DATABASE CORRUPTED FOR GROUP %s", gh->group_name);
  289. #ifdef HAVE_SYSLOG
  290.         openlog("nn", LOG_CONS, LOG_DAEMON);
  291.         syslog(LOG_ALERT, "database corrupted for newsgroup %s.",
  292.                gh->group_name);
  293.         closelog();
  294. #endif
  295.         user_delay(5);
  296.         }
  297.         if (mg_head != NULL) continue;
  298.         menu_return( ME_NEXT );
  299.     }
  300.  
  301.     if (mg_head == NULL) break;
  302.     }
  303.  
  304.     if (n_articles == 0) {
  305.     m_break_entry();
  306.     menu_return( ME_NO_ARTICLES );
  307.     }
  308.  
  309.     if (mg_head != NULL) {
  310.     if (!dont_sort_articles) sort_articles(-1);
  311.     init_group(mg_head);
  312.     }
  313.  
  314.  samemenu:
  315.     menu_cmd = CALL(menu)(print_header);
  316.  
  317.     if (menu_cmd == ME_REENTER_GROUP) {
  318.     if (group_level != 1) {
  319.         strcpy(delayed_msg, "can only unread groups at topmost level");
  320.         goto samemenu;
  321.     }
  322.     free_memory();
  323.     if (mg_head) gh = mg_head;
  324.     unread_at_reentry = 1;
  325.     goto reenter;
  326.     }
  327.  
  328.  menu_exit:
  329.  
  330.     n = 1;            /* must unsort articles */
  331.     if (mg_head != NULL) gh = mg_head;
  332.  
  333.     if (!no_update) {
  334.     do {
  335.         if (mask == NULL && (access_mode & ACC_ALSO_READ_ARTICLES) == 0
  336.         && first_art < 0) {
  337.         if (menu_cmd != ME_NO_ARTICLES) gh->group_flag &= ~G_NEW;
  338.         if ((gh->group_flag & G_UNSUBSCRIBED) == 0 || keep_unsub_long) {
  339.             if (n) sort_articles(0);
  340.             n = 0;
  341.             update_rc(gh);
  342.         }
  343.         }
  344.         gh->current_first = 0;
  345.     } while (mg_head != NULL && (gh = gh->merge_with) != NULL);
  346.     }
  347.  
  348.     killed_articles = o_killed;
  349.     restore_variables();
  350.     group_level--;
  351.  
  352.     return menu_cmd;
  353. }
  354.  
  355.  
  356. goto_group(command, ah, access_mode)
  357. int command;
  358. article_header *ah;
  359. flag_type access_mode;
  360. {
  361.     register group_header *gh;
  362.     char ans1, *answer, *mask, buffer[FILENAME], fbuffer[FILENAME];
  363.     article_number first;
  364.     memory_marker mem_marker;
  365.     group_header *orig_group;
  366.     int menu_cmd;
  367.     extern int menu(), file_completion();
  368.     extern article_header *get_menu_article();
  369.     extern int get_from_macro;
  370.     extern group_header *jump_to_group;
  371.  
  372. #define goto_return( cmd ) \
  373.     { menu_cmd = cmd; goto goto_exit; }
  374.  
  375.     m_startinput();
  376.  
  377.     gh = orig_group = current_group;
  378.  
  379.     mask = NULL;
  380.  
  381.     if (command == K_GOTO_GROUP)
  382.     goto get_group_name;
  383.  
  384.     if (command == K_ADVANCE_GROUP)
  385.     gh = gh->next_group;
  386.     else
  387.     gh = gh->prev_group;
  388.  
  389.     for (;;) {
  390.     if (gh == NULL)
  391.         goto_return(ME_NO_REDRAW);
  392.  
  393.     if (gh->first_db_article < gh->last_db_article && gh->current_first <= 0) {
  394.         sprintf(buffer, "%s%s%s) ",
  395.            (gh->group_flag & G_UNSUBSCRIBED) ? " UNSUB" : "",
  396.            (gh->group_flag & G_MERGE_HEAD) ? " MERGED" : "",
  397.            gh->unread_count <= 0 ? " READ" : "" );
  398.         buffer[0] = buffer[0] == ')' ? NUL : '(';
  399.         
  400.         prompt("\1Enter\1 %s %s?  (ABGNPy) ", gh->group_name, buffer);
  401.         
  402.         command = get_c();
  403.         if (command & GETC_COMMAND) goto_return(ME_REDRAW);
  404.         if (command == 'y') break;
  405.         command = menu_key_map[command];
  406.     }
  407.  
  408.     switch (command) {
  409.      case K_CONTINUE:
  410.      case K_CONTINUE_NO_MARK:
  411.         break;
  412.  
  413.      case K_ADVANCE_GROUP:
  414.         gh = gh->next_group;
  415.         continue;
  416.  
  417.      case K_BACK_GROUP:
  418.         gh = gh->prev_group;
  419.         continue;
  420.  
  421.      case K_GOTO_GROUP:
  422.         goto get_group_name;
  423.  
  424.      case K_PREVIOUS:
  425.         while (gh = gh->prev_group) {
  426.         if (gh->group_flag & G_MERGE_SUB) continue;
  427.         if (gh->group_flag & G_COUNTED) break;
  428.         if (gh->newsrc_line != gh->newsrc_orig) break;
  429.         }
  430.         continue;
  431.  
  432.      case K_NEXT_GROUP_NO_UPDATE:
  433.         while (gh = gh->next_group) {
  434.         if (gh->group_flag & G_MERGE_SUB) continue;
  435.         if (gh->group_flag & G_COUNTED) break;
  436.         if (gh->newsrc_line != gh->newsrc_orig) break;
  437.         }
  438.         continue;
  439.  
  440.      default:
  441.         goto_return(ME_NO_REDRAW);
  442.     }
  443.     break;
  444.     }
  445.  
  446.     if (gh == orig_group) goto_return(ME_NO_REDRAW);
  447.  
  448.     goto get_first;
  449.  
  450.  get_group_name:
  451.  
  452.     if (current_group == NULL) {
  453.     prompt("\1Enter Group or Folder\1 (+./~) ");
  454.     answer = get_s(NONE, NONE, "+./~", group_completion);
  455.     } else {
  456.     prompt("\1Group or Folder\1 (+./~ %%%s=sneN) ",
  457.            (gh->master_flag & M_AUTO_ARCHIVE) ? "@" : "");
  458.     strcpy(buffer, "++./0123456789~=% ");
  459.     if (gh->master_flag & M_AUTO_ARCHIVE) buffer[0] = '@';
  460.     answer = get_s(NONE, NONE, buffer, group_completion);
  461.     }
  462.  
  463.     if (answer == NULL) goto_return(ME_NO_REDRAW);
  464.  
  465.     if ((ans1 = *answer) == NUL || ans1 == SP) {
  466.     if (current_group == NULL) goto_return(ME_NO_REDRAW);
  467.     goto get_first;
  468.     }
  469.  
  470.     sprintf(buffer, "%c", ans1);
  471.  
  472.     switch (ans1) {
  473.  
  474.      case '@':
  475.     if (current_group == NULL ||
  476.         (current_group->master_flag & M_AUTO_ARCHIVE) == 0)
  477.         goto_return(ME_NO_REDRAW);
  478.     answer = current_group->archive_file;
  479.     goto get_folder;
  480.  
  481.      case '%':
  482.     if (current_group == NULL) goto_return(ME_NO_REDRAW);
  483.     if ((current_group->group_flag & G_FOLDER) == 0) {
  484.         if (!ah) {
  485.         prompt("\1READ\1");
  486.         if ((ah = get_menu_article()) == NULL)
  487.             goto_return(ME_NO_REDRAW);
  488.         }
  489.         if ((ah->flag & A_DIGEST) == 0) {
  490.         *group_file_name = NUL;
  491.         sprintf(fbuffer, "%s%ld", group_path_name, ah->a_number);
  492.         answer = fbuffer;
  493.         goto get_folder;
  494.         }
  495.     }
  496.  
  497.     msg("cannot split articles inside a folder or digest");
  498.     goto_return(ME_NO_REDRAW);
  499.  
  500.      case '.':
  501.      case '~':
  502.     strcat(buffer, "/");
  503.      case '+':
  504.      case '/':
  505.     if (!get_from_macro) {
  506.         prompt("\1Folder\1 ");
  507.         answer = get_s(NONE, buffer, NONE, file_completion);
  508.         if (answer == NULL || answer[0] == NUL) goto_return(ME_NO_REDRAW);
  509.     }
  510.     goto get_folder;
  511.     
  512.      case 'a':
  513.     if (answer[1] != NUL && strcmp(answer, "all")) break;
  514.     if (current_group == NULL) goto_return(ME_NO_REDRAW);
  515.     access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
  516.     first = 0;
  517.     goto more_articles;
  518.  
  519.      case 'u':
  520.     if (answer[1] != NUL && strcmp(answer, "unread")) break;
  521.     if (current_group == NULL) goto_return(ME_NO_REDRAW);
  522.     access_mode |= ACC_ORIG_NEWSRC;
  523.     first = gh->first_article + 1;
  524.     goto enter_new_level;
  525.  
  526.      case 'e':
  527.      case 'n':
  528.      case 's':
  529.     if (answer[1] != NUL && answer[1] != '=') break;
  530.     /* fall thru */
  531.      case '=':
  532.     if (current_group == NULL) goto_return(ME_NO_REDRAW);
  533.     goto get_mask;
  534.  
  535.      case '0':
  536.      case '1':
  537.      case '2':
  538.      case '3':
  539.      case '4':
  540.      case '5':
  541.      case '6':
  542.      case '7':
  543.      case '8':
  544.      case '9':
  545.     if (current_group == NULL) goto_return(ME_NO_REDRAW);
  546.     if (gh->current_first <= gh->first_db_article) {
  547.         msg("No extra articles");
  548.         flush_input();
  549.         goto_return(ME_NO_REDRAW);
  550.     }
  551.     if (!get_from_macro) {
  552.         prompt("\1Number of extra articles\1 max %ld: ",
  553.            gh->current_first - gh->first_db_article);
  554.         sprintf(buffer, "%c", ans1);
  555.         answer = get_s(NONE, buffer, NONE, NULL_FCT);
  556.         if (answer == NULL || *answer ==  NUL) goto_return(ME_NO_REDRAW);
  557.     }
  558.     first = gh->current_first - atol(answer);
  559.     goto more_articles;
  560.  
  561.      default:
  562.     break;
  563.     }
  564.  
  565.     if ((gh = lookup(answer)) == NULL) {
  566.     msg("No group named %s", answer);
  567.     goto_return(ME_NO_REDRAW);
  568.     }
  569.  
  570.  
  571.  get_first:
  572.  
  573.     m_advinput();
  574.  
  575.     if (gh->master_flag & M_BLOCKED ||
  576.     gh->last_db_article == 0 ||
  577.     gh->last_db_article < gh->first_db_article) {
  578.     msg("Group %s is %s", gh->group_name,
  579.         (gh->master_flag & M_BLOCKED) ? "blocked" : "empty");
  580.     goto_return(ME_NO_REDRAW);
  581.     }
  582.  
  583.     if (gh != orig_group) {
  584.     if (gh->current_first > 0) {
  585.         msg("Group %s already active", gh->group_name);
  586.         goto_return(ME_NO_REDRAW);
  587.     }
  588.     gh->current_first = gh->last_db_article + 1;
  589.     }
  590.  
  591.     ans1 = ah ? 's' : 'a';
  592.     if (gh != orig_group) {
  593.     if (gh->unread_count > 0) ans1 = 'j';
  594.     } else {
  595.     if (gh->unread_count > 0 && gh->current_first > (gh->first_article + 1))
  596.         ans1 = 'u';
  597.     else if (gh->current_first <= gh->first_db_article)
  598.         ans1 = 's';     /* no more articles to read */
  599.     }
  600.  
  601.     prompt("\1Number of%s articles\1 (%sua%ssne)  (%c) ",
  602.        gh == orig_group ? " extra" : "",
  603.        (gh->unread_count > 0) ? "j" : "",
  604.        (gh->master_flag & M_AUTO_ARCHIVE) ? "@" : "",
  605.        ans1);
  606.  
  607.     if (novice)
  608.     msg("Use: j)ump u)nread a)ll @)archive s)ubject n)ame e)ither or number");
  609.  
  610.     answer = get_s(NONE, NONE, " jua@s=ne", NULL_FCT);
  611.     if (answer == NULL) goto_return(ME_NO_REDRAW);
  612.     if (*answer == NUL || *answer == SP) answer = &ans1;
  613.  
  614.     switch (*answer) {
  615.      case '@':
  616.     if ((gh->master_flag & M_AUTO_ARCHIVE) == 0) {
  617.         msg("No archive");
  618.         goto_return(ME_NO_REDRAW);
  619.     }
  620.     answer = gh->archive_file;
  621.     goto get_folder;
  622.  
  623.      case 'u':
  624.     access_mode |= ACC_ORIG_NEWSRC;
  625.     first = gh->first_article + 1;
  626.     goto enter_new_level;
  627.  
  628.      case 'j':
  629.     if (gh == orig_group || gh->unread_count <= 0) {
  630.         msg("Cannot jump - no unread articles");
  631.         goto_return(ME_NO_REDRAW);
  632.     }
  633.     jump_to_group = gh;
  634.     goto_return(ME_QUIT);
  635.  
  636.      case 'a':
  637.     first = 0;
  638.     access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
  639.     goto more_articles;
  640.  
  641.      case '=':
  642.      case 's':
  643.      case 'n':
  644.      case 'e':
  645.     goto get_mask;
  646.  
  647.      case '0':
  648.      case '1':
  649.      case '2':
  650.      case '3':
  651.      case '4':
  652.      case '5':
  653.      case '6':
  654.      case '7':
  655.      case '8':
  656.      case '9':
  657.     first = gh->current_first - atol(answer);
  658.     goto more_articles;
  659.  
  660.      default:
  661.     ding();
  662.     goto_return(ME_NO_REDRAW);
  663.     }
  664.  
  665.  
  666.  more_articles:
  667.     if (first > gh->last_db_article) goto_return(ME_NO_REDRAW);
  668.  
  669.     if (gh != orig_group) goto enter_new_level;
  670.  
  671.     if (gh->current_first <= gh->first_db_article) {
  672.     msg("No extra articles");
  673.     goto_return(ME_NO_REDRAW);
  674.     }
  675.  
  676.     if (access_group(gh, first, gh->current_first == gh->first_article + 1 ?
  677.              gh->last_db_article : gh->current_first - 1,
  678.              ACC_EXTRA_ARTICLES | ACC_ALSO_CROSS_POSTINGS |
  679.              ACC_ALSO_READ_ARTICLES | ACC_ONLY_READ_ARTICLES,
  680.              (char *)NULL) < 0) {
  681.     msg("Cannot read extra articles (now)");
  682.     goto_return(ME_NO_REDRAW);
  683.     }
  684.  
  685.     gh->current_first = first;
  686.     goto_return(ME_REDRAW);
  687.         
  688.  get_folder:
  689.     m_endinput();
  690.     if (strcmp(answer, "+") == 0)
  691.     answer = (gh->save_file != NULL) ? gh->save_file : default_save_file;
  692.     if (!expand_file_name(buffer, answer, 1)) goto_return (ME_NO_REDRAW);
  693.     menu_cmd = folder_menu(buffer);
  694.     gh = NULL;
  695.     goto goto_exit;
  696.  
  697.  get_mask:
  698.     first = 0;
  699.     access_mode |= ACC_ALSO_READ_ARTICLES | ACC_ALSO_CROSS_POSTINGS;
  700.     switch (*answer) {
  701.      case '=':
  702.     *answer = 's';
  703.      case 's':
  704.     access_mode |= ACC_ON_SUBJECT;
  705.     break;
  706.      case 'n':
  707.     access_mode |= ACC_ON_SENDER;
  708.     break;
  709.      case 'e':
  710.     access_mode |= ACC_ON_SUBJECT | ACC_ON_SENDER;
  711.     break;
  712.     }
  713.     
  714.     if (get_from_macro || answer[1] == '=') {
  715.     mask = answer + 1;
  716.     if (*mask == '=') mask++;
  717.     } else {
  718.     prompt("%c=", *answer);
  719.     mask = get_s(ah == NULL ? NONE :
  720.              (access_mode & ACC_ON_SUBJECT ? ah->subject : ah->sender),
  721.              NONE, ah ? NONE : "%=", NULL_FCT);
  722.     if (mask == NULL) goto_return(ME_NO_REDRAW);
  723.     if (*mask == '%' || *mask == '=') {
  724.         if ((ah = get_menu_article()) == 0) goto_return(ME_NO_REDRAW);
  725.         *mask = NUL;
  726.     }
  727.     }
  728.  
  729.     if (*mask == NUL) {
  730.     if (ah == NULL) goto_return(ME_NO_REDRAW);
  731.     strncpy(mask, (access_mode & ACC_ON_SUBJECT) ? ah->subject : ah->sender, GET_S_BUFFER);
  732.     mask[GET_S_BUFFER-1] = NUL;
  733.     }
  734.  
  735.     if (*mask) {
  736.     if (case_fold_search) fold_string(mask);
  737.     } else
  738.     mask = NULL;
  739.  
  740.  enter_new_level:
  741.     mark_memory(&mem_marker);
  742.     m_endinput();
  743.     menu_cmd = group_menu(gh, first, access_mode, mask, menu);
  744.     release_memory(&mem_marker);
  745.  
  746. goto_exit:
  747.     if (gh != orig_group)
  748.     if (orig_group) init_group(orig_group);
  749.  
  750.     m_endinput();
  751.     return menu_cmd;
  752. }
  753.  
  754. static merged_header()
  755. {
  756.     so_printxy(0, 0, "MERGED NEWS GROUPS:  %d ARTICLES", n_articles);
  757.     clrline();
  758.  
  759.     return 1;
  760. }
  761.  
  762. merge_and_read(access_mode, mask)
  763. flag_type access_mode;
  764. char *mask;
  765. {
  766.     register group_header *gh;
  767.     group_header dummy_group;
  768.     time_t t1, t2;
  769.     long kb = 0, kbleft = 0;
  770.  
  771.     time(&t1); t2 = 0;
  772.  
  773.     free_memory();
  774.  
  775.     access_mode |= ACC_DONT_SORT_ARTICLES | ACC_MERGED_MENU;
  776.     if (!seq_cross_filtering)
  777.     if (also_read_articles || mask || also_cross_postings)
  778.         access_mode |= ACC_ALSO_CROSS_POSTINGS;
  779.     if (dont_split_digests)
  780.     access_mode |= ACC_DONT_SPLIT_DIGESTS;
  781.  
  782.     Loop_Groups_Sequence(gh) {
  783.     if (gh->group_flag & G_FOLDER) continue;
  784.     if (gh->master_flag & M_NO_DIRECTORY) continue;
  785.     if ((gh->group_flag & G_UNSUBSCRIBED) && !also_unsub_groups)
  786.         continue;
  787.     if (!also_read_articles && gh->last_article >= gh->last_db_article)
  788.         continue;
  789.  
  790.     kbleft += gh->data_write_offset;
  791.     }
  792.  
  793.     Loop_Groups_Sequence(gh) {
  794.     if (s_hangup || s_keyboard) break;
  795.  
  796.     if (gh->group_flag & G_FOLDER) {
  797.         printf("\n\rIgnoring folder: %s\n\r", gh->group_name);
  798.         continue;
  799.     }
  800.  
  801.     if (gh->master_flag & M_NO_DIRECTORY) continue;
  802.     if ((gh->group_flag & G_UNSUBSCRIBED) && !also_unsub_groups)
  803.         continue;
  804.  
  805.     if (also_read_articles)
  806.         access_mode |= ACC_ALSO_READ_ARTICLES;
  807.     else
  808.         if (gh->last_article >= gh->last_db_article)
  809.         continue;
  810.  
  811.     if (init_group(gh) <= 0) continue;
  812.  
  813.     if (t2 > 2 && kb > 50000) {
  814.         printf("\r%4lds\t%lds\t%s", kbleft/(kb/t2), (long)t2, gh->group_name);
  815.     } else
  816. #ifdef KBYTE_PER_SECOND
  817.         printf("\r%4lds\t%lds\t%s", kbleft/(KBYTE_PER_SECOND*1024)),
  818.         (long)t2, gh->group_name);
  819. #else
  820.         printf("\r\t%lds\t%s", (long)t2, gh->group_name);
  821. #endif
  822.     clrline();
  823.  
  824.     access_group(gh, (article_number)(-1), gh->last_db_article,
  825.              access_mode, mask);
  826.  
  827.     time(&t2); t2 -= t1;
  828.     kb += gh->data_write_offset;
  829.     kbleft -= gh->data_write_offset;
  830.     }
  831.     merge_memory();
  832.     if (n_articles == 0) return;
  833.     if (!dont_sort_articles) sort_articles(-1);
  834.  
  835.     dummy_group.group_flag = G_FAKED;
  836.     dummy_group.master_flag = 0;
  837.     dummy_group.save_file = NULL;
  838.     dummy_group.group_name = "dummy";
  839.     dummy_group.kill_list = NULL;
  840.  
  841.     current_group = &dummy_group;
  842.  
  843.     kb = (kb + 1023) >> 10;
  844.     sprintf(delayed_msg, "Read %ld articles in %ld seconds (%ld kbyte/s)",
  845.         (long)db_read_counter, (long)t2, t2 > 0 ? kb/t2 : kb);
  846.  
  847.     menu(merged_header);
  848.  
  849.     free_memory();
  850. }
  851.  
  852. unsubscribe(gh)
  853. group_header *gh;
  854. {
  855.     if (no_update) {
  856.     msg("nn started in \"no update\" mode");
  857.     return 0;
  858.     }
  859.  
  860.     if (gh->group_flag & G_FOLDER) {
  861.     msg("cannot unsubscribe to a folder");
  862.     return 0;
  863.     }
  864.  
  865.     if (gh->group_flag & G_UNSUBSCRIBED) {
  866.     prompt("Already unsubscribed.  \1Resubscribe to\1 %s ? ",
  867.            gh->group_name);
  868.     if (yes(0) <= 0) return 0;
  869.  
  870.     add_to_newsrc(gh);
  871.     add_unread(gh, 1);
  872.     } else {
  873.     prompt("\1Unsubscribe to\1 %s ? ", gh->group_name);
  874.     if (yes(0) <= 0) return 0;
  875.  
  876.     add_unread(gh, -1);
  877.     update_rc_all(gh, 1);
  878.     }
  879.  
  880.     return 1;
  881. }
  882.  
  883. static disp_group(gh)
  884. group_header *gh;
  885. {
  886.     if (pg_next() < 0) return -1;
  887.  
  888.     printf("%c%6ld%c%s%s%s",
  889.        (gh->group_flag & G_MERGED) == 0 ? ' ' :
  890.        (gh->group_flag & G_MERGE_HEAD) ? '&' : '+',
  891.  
  892.        (long)(gh->unread_count),
  893.  
  894.        (gh == current_group) ? '*' : ' ',
  895.  
  896.        gh->group_name,
  897.  
  898.        (gh->group_flag & G_NEW) ? " NEW" :
  899.        (gh->group_flag & G_UNSUBSCRIBED) ? " (!)" : "",
  900.  
  901.        gh->enter_macro ? " %" : "");
  902.  
  903.     return 0;
  904. }
  905.  
  906. /*
  907.  * amount interpretation:
  908.  *    -1    presentation sequence, unread,subscribed+current
  909.  *     0    unread,subscribed
  910.  *    1    unread,all
  911.  *    2    all,all 3=>unsub
  912.  */
  913.  
  914. group_overview(amount)
  915. int amount;
  916. {
  917.     register group_header *gh;
  918.  
  919.     clrdisp();
  920.  
  921.     pg_init(0, 2);
  922.  
  923.     if (amount < 0) {
  924.     Loop_Groups_Sequence(gh) {
  925.         if (gh->group_flag & G_FAKED) continue;
  926.         if (gh->master_flag & M_NO_DIRECTORY) continue;
  927.         if (gh != current_group)
  928.         if ((gh->group_flag & G_COUNTED) == 0) continue;
  929.         if (disp_group(gh) < 0) break;
  930.     }
  931.     } else
  932.     Loop_Groups_Sorted(gh) {
  933.     if (gh->master_flag & (M_NO_DIRECTORY | M_IGNORE_GROUP)) continue;
  934.     if (amount <= 1 && gh->unread_count <= 0) continue;
  935.     if (amount == 0 && (gh->group_flag & G_UNSUBSCRIBED)) continue;
  936.     if (amount == 3 && (gh->group_flag & G_UNSUBSCRIBED) == 0) continue;
  937.  
  938.     if (disp_group(gh) < 0) break;
  939.     }
  940.  
  941.     pg_end();
  942. }
  943.