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

  1. /*
  2.  *    (c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
  3.  *
  4.  *    .newsrc parsing and update.
  5.  */
  6.  
  7. #include "config.h"
  8. #include "options.h"
  9. #include "regexp.h"
  10. #include "keymap.h"
  11. #include "nn_term.h"
  12. #include "articles.h"
  13.  
  14. /* newsrc.c */
  15.  
  16. #ifdef __STDC__
  17. struct rc_info;
  18. #endif
  19.  
  20. static dump_file __APROTO((char *path, int mode));
  21. static void dump_newsrc __APROTO((void));
  22. static void dump_select __APROTO((void));
  23. static time_t get_last_new __APROTO((void));
  24. static void update_last_new __APROTO((group_header *lastg));
  25. static article_number get_last_article __APROTO((group_header *gh));
  26. static void init_rctest __APROTO((group_header *gh, register struct rc_info *r));
  27. static attr_type rctest __APROTO((register article_header *ah, register struct rc_info *r));
  28. static void append __APROTO(());
  29. static void append_range __APROTO((int pp, int delim, article_number rmin, article_number rmax));
  30. static void begin_rc_update __APROTO((register group_header *gh));
  31. static void end_rc_update __APROTO((register group_header *gh));
  32. static void mark_article __APROTO((register article_header *ah, int how));
  33.  
  34.  
  35. #ifdef sel
  36. /* Remove Gould SELbus software name collision */
  37. #undef sel
  38. #endif
  39.  
  40. #define TR(arg) tprintf arg
  41.  
  42. import char *news_lib_directory, *db_directory;
  43. import char *home_directory;
  44. import char *pname;
  45.  
  46. import int  verbose;
  47. import int  silent;
  48.  
  49. import long n_selected;
  50. extern char *tmp_directory;
  51.  
  52. export int  keep_rc_backup = 1;
  53. export char *bak_suffix = ".bak";
  54. export char *initial_newsrc_path = ".defaultnewsrc";
  55.  
  56. export int  no_update = 0;
  57. export int  use_selections = 1;
  58. export int  quick_unread_count = 1; /* make a quick count of unread art. */
  59. export int  newsrc_update_freq = 1; /* how often to write .newsrc */
  60. export char *newsrc_file = NULL;
  61.  
  62. #define RCX_NEVER    0 /* ignore missing groups */
  63. #define RCX_HEAD    1 /* prepend missing groups to .newsrc when read */
  64. #define    RCX_TAIL    2 /* append missing groups to .newsrc when read */
  65. #define RCX_TIME    3 /* append NEW groups as they arrive */
  66. #define RCX_TIME_CONF    4 /* append NEW groups with confirmation */
  67. #define RCX_RNLAST    5 /* .rnlast compatible functionality */
  68.  
  69. export int  new_group_action = RCX_TIME; /* append new groups to .newsrc */
  70. export int  keep_unsubscribed = 1; /* keep unsubscribed groups in .newsrc */
  71. export int  keep_unsub_long = 0; /* keep unread in unsubscribed groups */
  72.  
  73. export int  tidy_newsrc = 0;       /* remove obsolete groups from .newsrc */
  74.  
  75. export int  auto_junk_seen = 1;    /* junk seen articles ... */
  76. export int  conf_junk_seen = 0; /* ... if confirmed by user ... */
  77. export int  retain_seen_status = 0; /* ... or remember seen articles. */
  78.  
  79. export long unread_articles;    /* estimate of unread articles */
  80. export int  unread_groups;
  81.  
  82. export group_header *rc_sequence = NULL;
  83.  
  84. static char *sel_path = NULL;
  85.  
  86.  
  87. /* delimitors on newsrc lines */
  88.  
  89. #define RC_SUBSCR    ':'    /* subscription to group */
  90. #define RC_UNSUBSCR    '!'    /* no subscription to group */
  91.  
  92. #define RC_DELIM    ','    /* separator on rc lines */
  93. #define RC_RANGE    '-'    /* range */
  94.  
  95. /* delimitors on select lines */
  96.  
  97. #define SEL_RANGE    '-'    /* range */
  98. #define SEL_SELECT    ','    /* following articles are selected */
  99. #define SEL_LEAVE    '+'    /* following articles are left over */
  100. #define SEL_SEEN    ';'    /* following articles are seen */
  101. #define SEL_UNREAD    '~'    /* in digests */
  102. #define SEL_DIGEST    '('    /* start digest list */
  103. #define SEL_END_DIGEST    ')'    /* end digest list */
  104. #define SEL_NEW        '&'    /* new group (group.name&nnn) */
  105.  
  106. #define END_OF_LIST    10000000L /* Greater than any article number */
  107.  
  108. /* line buffers */
  109.  
  110. #define RC_LINE_MAX    8192
  111.  
  112. static char rcbuf[RC_LINE_MAX];
  113. static char selbuf[RC_LINE_MAX];
  114.  
  115. static group_header *rc_seq_tail = NULL;
  116.  
  117. static int newsrc_update_count = 0, select_update_count = 0;
  118.  
  119. #define DM_NEWSRC    0
  120. #define DM_SELECT    1
  121. #define DM_ORIG_NEWSRC    2
  122. #define DM_ORIG_SELECT    3
  123.  
  124. static int
  125. dump_file(path, mode)
  126. char *path;
  127. int mode;
  128. {
  129.     FILE *f = NULL;
  130.     register group_header *gh;
  131.     char *line = NULL;
  132.  
  133.     Loop_Groups_Newsrc(gh) {
  134.     switch (mode) {
  135.      case DM_NEWSRC:
  136.         if (tidy_newsrc) {
  137.         if ((gh->master_flag & M_VALID) == 0)
  138.             continue;
  139.         if (!keep_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED))
  140.             continue;
  141.         }
  142.         line = gh->newsrc_line;
  143.         break;
  144.      case DM_SELECT:
  145.         if (tidy_newsrc && (gh->master_flag & M_VALID) == 0) continue;
  146.         if (gh->group_flag & G_UNSUBSCRIBED) continue;
  147.         line = gh->select_line;
  148.         break;
  149.      case DM_ORIG_NEWSRC:
  150.         line = gh->newsrc_orig;
  151.         break;
  152.      case DM_ORIG_SELECT:
  153.         line = gh->select_orig;
  154.         break;
  155.     }
  156.     if (line == NULL) continue;
  157.     if (f == NULL)
  158.         f = open_file(path, OPEN_CREATE|MUST_EXIST);
  159.     fputs(line, f);
  160.     }
  161.     if (f != NULL) 
  162.     if (fclose(f) == EOF) return -1;
  163.     return 0;
  164. }
  165.  
  166.  
  167. static void
  168. dump_newsrc()
  169. {
  170.     char bak[FILENAME];
  171.     static int first = 1;
  172.  
  173.     if (no_update) return;
  174.     if (++newsrc_update_count < newsrc_update_freq) return;
  175.  
  176.     if (first && keep_rc_backup) {
  177.     sprintf(bak, "%s%s", newsrc_file, bak_suffix);
  178.     if (dump_file(bak, DM_ORIG_NEWSRC))
  179.         nn_exitmsg(1, "Cannot backup %s", newsrc_file);
  180.     first = 0;
  181.     }
  182.  
  183.     if (dump_file(newsrc_file, DM_NEWSRC)) {
  184.     char temp[FILENAME];
  185.     sprintf(temp, "%s/newsrc-%d", tmp_directory, process_id);
  186.     if (dump_file(temp, DM_NEWSRC))
  187.         nn_exitmsg(1, "Cannot update %s -- restore %s file!!!",
  188.                newsrc_file, bak_suffix);
  189.     else
  190.         nn_exitmsg(1, "Cannot update %s -- saved in %s", newsrc_file, temp);
  191.     }
  192.     newsrc_update_count = 0;
  193. }
  194.  
  195. static void
  196. dump_select()
  197. {
  198.     char bak[FILENAME];
  199.     static int first = 1;
  200.  
  201.     if (no_update) return;
  202.     if (++select_update_count < newsrc_update_freq) return;
  203.  
  204.     if (first && keep_rc_backup) {
  205.     sprintf(bak, "%s%s", sel_path, bak_suffix);
  206.     dump_file(bak, DM_ORIG_SELECT);
  207.     first = 0;
  208.     }
  209.  
  210.     dump_file(sel_path, DM_SELECT);
  211.  
  212.     select_update_count = 0;
  213. }
  214.  
  215. #define RN_LAST_GROUP_READ    0
  216. #define RN_LAST_TIME_RUN    1
  217. #define RN_LAST_ACTIVE_SIZE    2
  218. #define RN_LAST_CREATION_TIME    3
  219. #define RN_LAST_NEW_GROUP    4
  220. #define RN_ACTIVE_TIMES_OFFSET    5
  221.  
  222. #define MAX_RNLAST_LINE 6
  223.  
  224. static char *rnlast_line[MAX_RNLAST_LINE];
  225. #define rnlast_path relative(home_directory, ".rnlast")
  226.  
  227. static time_t get_last_new()
  228. {
  229.     FILE *lf;
  230.     char buf[FILENAME];
  231.     register int i;
  232.     time_t t;
  233.  
  234.     if (new_group_action == RCX_RNLAST) {
  235.     lf = open_file(rnlast_path, OPEN_READ);
  236.     if (lf == NULL) goto no_file;
  237.  
  238.     for (i = 0; i < MAX_RNLAST_LINE; i++) {
  239.         if (fgets(buf, FILENAME, lf) == NULL) break;
  240.         rnlast_line[i] = copy_str(buf);
  241.     }
  242.     if (i != MAX_RNLAST_LINE) {
  243.         while (i <= MAX_RNLAST_LINE) {
  244.         rnlast_line[i] = copy_str("");
  245.         i++;
  246.         }
  247.         fclose(lf);
  248.         return (time_t)atol(rnlast_line[RN_LAST_TIME_RUN]);
  249.     }
  250.     fclose(lf);
  251.     return (time_t)atol(rnlast_line[RN_LAST_CREATION_TIME]);
  252.     }
  253.  
  254.     lf = open_file(relative(nn_directory, "LAST"), OPEN_READ);
  255.     if (lf == NULL) goto no_file;
  256.     if (fgets(buf, FILENAME, lf) == NULL) goto no_file;
  257.  
  258.     fclose(lf);
  259.     return (time_t)atol(buf);
  260.  
  261.  no_file:
  262.     if (lf != NULL) fclose(lf);
  263.     t = file_exist(newsrc_file, (char *)NULL);
  264.     return t > 0 ? t : (time_t)(-1);
  265. }
  266.  
  267. static void
  268. update_last_new(lastg)
  269. group_header *lastg;
  270. {
  271.     FILE *lf;
  272.     register int i;
  273.     struct stat st;
  274.  
  275.     if (no_update) return;
  276.  
  277.     if (new_group_action == RCX_RNLAST) {
  278.     if (rnlast_line[0] == NULL)
  279.         for (i = 0; i < MAX_RNLAST_LINE; i++)
  280.         rnlast_line[i] = copy_str("");
  281.     lf = open_file(rnlast_path, OPEN_CREATE|MUST_EXIST);
  282.     fputs(rnlast_line[RN_LAST_GROUP_READ], lf);    /* as good as any */
  283.     fprintf(lf, "%ld\n", (long)cur_time()); /* RN_LAST_TIME_RUN */
  284.     fprintf(lf, "%ld\n", (long)master.last_size); /* RN_LAST_ACTIVE_SIZE */
  285.  
  286.     fprintf(lf, "%ld\n", (long)lastg->creation_time); /* RN_LAST_CREATION_TIME */
  287.     fprintf(lf, "%s\n",lastg->group_name); /* RN_LAST_NEW_GROUP */
  288.  
  289.     if (stat(relative(news_lib_directory, "active.times"), &st) == 0)
  290.         fprintf(lf, "%ld\n", (long)st.st_size);
  291.     else /* can't be perfect -- don't update */
  292.         fputs(rnlast_line[RN_ACTIVE_TIMES_OFFSET], lf);
  293.     for (i = 0; i < MAX_RNLAST_LINE; i++) freeobj(rnlast_line[i]);
  294.     } else {
  295.     lf = open_file(relative(nn_directory, "LAST"), OPEN_CREATE|MUST_EXIST);
  296.     fprintf(lf, "%ld\n%s\n", (long)lastg->creation_time, lastg->group_name);
  297.     }
  298.  
  299.     fclose(lf);
  300. }
  301.  
  302. static article_number get_last_article(gh)
  303. group_header *gh;
  304. {
  305.     register char *line;
  306.  
  307.     if ((line = gh->newsrc_line) == NULL) return -1;
  308.  
  309.     line += gh->group_name_length+1;
  310.     while (*line && isspace(*line)) line++;
  311.     if (*line == NUL) return -1;
  312.  
  313.     if (line[0] == '1') {
  314.     if (line[1] == RC_RANGE)
  315.         return atol(line+2);
  316.     if (!isdigit(line[1])) return 1;
  317.     }
  318.     return 0;
  319. }
  320.  
  321.  
  322. void
  323. visit_rc_file()
  324. {
  325.     FILE *rc, *sel;
  326.     register group_header *gh;
  327.     int subscr;
  328.     register char *bp;
  329.     register int c;
  330.     char bak[FILENAME];
  331.     time_t last_new_group = 0, rc_age, newsrc_age;
  332.     group_header *last_new_gh = NULL;
  333.  
  334.     if (newsrc_file == NULL)
  335.     newsrc_file = home_relative(".newsrc");
  336.  
  337.     sel_path = mk_file_name(nn_directory, "select");
  338.  
  339.     Loop_Groups_Header(gh) {
  340.     gh->newsrc_line = NULL;
  341.     gh->newsrc_orig = NULL;
  342.     gh->select_line = NULL;
  343.     gh->select_orig = NULL;
  344.     }
  345.  
  346.     if ((rc_age = file_exist(relative(nn_directory, "rc"), (char *)NULL))) {
  347.     if (who_am_i != I_AM_NN)
  348.         nn_exitmsg(1, "A release 6.3 rc file exists. Run nn to upgrade");
  349.  
  350.     sprintf(bak, "%s/upgrade_rc", lib_directory);
  351.  
  352.     if ((newsrc_age = file_exist(newsrc_file, (char *)NULL)) == 0) {
  353.         display_file("adm.upgrade1", CLEAR_DISPLAY);
  354.     } else {
  355.         if (rc_age + 60 > newsrc_age) {
  356.         /* rc file is newest (or .newsrc does not exist) */
  357.         display_file("adm.upgrade2", CLEAR_DISPLAY);
  358.         prompt("Convert rc file to .newsrc now? ");
  359.         if (yes(1) <= 0) nn_exit(0);
  360.         } else {
  361.         /* .newsrc file is newest */
  362.         display_file("adm.upgrade3", CLEAR_DISPLAY);
  363.         prompt("Use current .newsrc file? ");
  364.         if (yes(1) > 0) {
  365.             strcat(bak, " n");
  366.         } else {
  367.             display_file("adm.upgrade4", CLEAR_DISPLAY);
  368.             prompt("Convert rc file to .newsrc? ");
  369.             if (yes(1) <= 0)
  370.             nn_exitmsg(0, "Then you will have to upgrade manually");
  371.         }
  372.         }
  373.     }
  374.  
  375.     tprintf("\r\n\n");
  376.     system(bak);
  377.     any_key(prompt_line);
  378.     }
  379.  
  380.     rc = open_file(newsrc_file, OPEN_READ);
  381.     if (rc != NULL) {
  382.     fseek(rc, (off_t)0, 2);
  383.     if (ftell(rc))
  384.         rewind(rc);
  385.     else {
  386.         fclose(rc);
  387.         rc = NULL;
  388.     }
  389.     }
  390.  
  391.     if (rc == NULL) {
  392.     sprintf(bak, "%s%s", newsrc_file, bak_suffix ? bak_suffix : ".bak");
  393.     if ((rc = open_file(bak, OPEN_READ))) {
  394.         int ans;
  395.         time_t rc_mtime;
  396.         
  397.         tprintf("\nThere is no .newsrc file - restore backup? (y) ");
  398.         if ((ans = yes(0)) < 0) nn_exit(1);
  399.         if (!ans) {
  400.         tprintf("\nConfirm building .newsrc from scratch: (y/n) ");
  401.         if ((ans = yes(1)) <= 0) nn_exit(1);
  402.         fclose(rc);
  403.         rc = NULL;
  404.         } else {
  405.           switch (new_group_action) {
  406.             case RCX_TIME:
  407.             case RCX_TIME_CONF:
  408.             case RCX_RNLAST:
  409.            last_new_group = get_last_new();
  410.            rc_mtime = (rc ? m_time(rc) : 0);
  411.            if (last_new_group < 0 || rc_mtime < last_new_group)
  412.                last_new_group = rc_mtime;
  413.           }
  414.         }
  415.     }
  416.     }
  417.  
  418.     if (rc == NULL) {
  419.  
  420.     last_new_group = -1;    /* ignore LAST & .rnlast if no .newsrc */
  421.     rc = open_file_search_path(initial_newsrc_path, OPEN_READ);
  422.     if (rc == NULL) goto new_user;
  423.     /* ignore groups not in the initial newsrc file */
  424.     last_new_gh = ACTIVE_GROUP(master.number_of_groups - 1);
  425.     last_new_group = last_new_gh->creation_time;
  426.     }
  427.  
  428.     while (fgets(rcbuf, RC_LINE_MAX, rc) != NULL) {
  429.     gh = NULL;
  430.     subscr = 0;
  431.     for (bp = rcbuf; (c = *bp); bp++) {
  432.         if (isspace(c)) break;    /* not a valid line */
  433.  
  434.         if (c == RC_UNSUBSCR || c == RC_SUBSCR) {
  435.         subscr = (c == RC_SUBSCR);
  436.         *bp = NUL;
  437.         gh = lookup_no_alias(rcbuf);
  438.         *bp = c;
  439.         break;
  440.         }
  441.     }
  442.  
  443.     if (gh == NULL) {
  444.         gh = newobj(group_header, 1);
  445.         gh->group_flag |= G_FAKED;
  446.         gh->master_flag |= M_VALID;
  447.     }
  448.  
  449.     if (gh->master_flag & M_ALIASED) continue;
  450.  
  451.     if (rc_seq_tail == NULL)
  452.         rc_sequence = rc_seq_tail = gh;
  453.     else {
  454.         rc_seq_tail->newsrc_seq = gh;
  455.         rc_seq_tail = gh;
  456.     }
  457.  
  458.     gh->newsrc_orig = gh->newsrc_line = copy_str(rcbuf);
  459.     if (gh->group_flag & G_FAKED)
  460.         gh->group_name = gh->newsrc_line;
  461.     else
  462.         if (!subscr)
  463.         gh->group_flag |= G_UNSUBSCRIBED;
  464.     }
  465.     fclose(rc);
  466.  
  467.  new_user:
  468.     Loop_Groups_Header(gh) {
  469.     if (gh->master_flag & M_IGNORE_GROUP) continue;
  470.     if (gh->group_flag & G_UNSUBSCRIBED) continue;
  471.     if (gh->newsrc_line == NULL) {
  472.         char buf[FILENAME];
  473.  
  474.         /* NEW GROUP - ADD TO NEWSRC AS APPROPRIATE */
  475.  
  476.         switch (new_group_action) {
  477.          case RCX_NEVER:
  478.         /* no not add new groups */
  479.         gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
  480.         continue;
  481.  
  482.          case RCX_HEAD:
  483.         /* insert at top */
  484.         gh->newsrc_seq = rc_sequence;
  485.         rc_sequence = gh;
  486.         break;
  487.  
  488.  
  489.          case RCX_TIME:
  490.          case RCX_TIME_CONF:
  491.          case RCX_RNLAST:
  492.         if (last_new_group == 0) {
  493.             last_new_group = get_last_new();
  494.             if (last_new_group < 0 && new_group_action != RCX_RNLAST) {
  495.             /* maybe this is a first time rn convert ? */
  496.             int nga = new_group_action;
  497.             new_group_action = RCX_RNLAST;
  498.             last_new_group = get_last_new();
  499.             new_group_action = nga;
  500.             }
  501.         }
  502.  
  503.         if (gh->creation_time <= last_new_group) {
  504.             /* old groups not in .newsrc are unsubscribed */
  505.             gh->group_flag |= G_UNSUBSCRIBED;
  506.             continue;
  507.         }
  508.  
  509.         if (last_new_gh == NULL || last_new_gh->creation_time <= gh->creation_time)
  510.             last_new_gh = gh;
  511.  
  512.         if (new_group_action != RCX_TIME && !no_update) {
  513.             tprintf("\nNew group: %s -- append to .newsrc? (y)",
  514.                gh->group_name);
  515.             if (yes(0) <= 0) {
  516.             gh->group_flag |= G_DONE | G_UNSUBSCRIBED;
  517.             continue;
  518.             }
  519.         }
  520.         sprintf(buf, "%s:\n", gh->group_name);
  521.         /* to avoid fooling the LAST mechanism, we must fake */
  522.         /* that the group was also in the original .newsrc */
  523.  
  524.         gh->newsrc_orig = gh->newsrc_line = copy_str(buf);
  525.         newsrc_update_count++;
  526.  
  527.         /*FALLTHRU*/
  528.          case RCX_TAIL:
  529.         /* insert at bottom */
  530.         if (rc_seq_tail == NULL)
  531.             rc_sequence = rc_seq_tail = gh;
  532.         else {
  533.             rc_seq_tail->newsrc_seq = gh;
  534.             rc_seq_tail = gh;
  535.         }
  536.         break;
  537.         }
  538.  
  539.         gh->last_article = -1;
  540.     } else
  541.         gh->last_article = get_last_article(gh);
  542.  
  543.     if (gh->last_article < 0) {
  544.         gh->group_flag |= G_NEW;
  545.         gh->last_article = gh->first_db_article - 1;
  546.     } else
  547.         if (gh->first_db_article > gh->last_article)
  548.         gh->last_article = gh->first_db_article - 1;
  549.  
  550.     if (gh->last_article < 0) gh->last_article = 0;
  551.     gh->first_article = gh->last_article;
  552.     }
  553.  
  554.     if (rc_seq_tail)
  555.     rc_seq_tail->newsrc_seq = NULL;
  556.  
  557.     if (last_new_gh != NULL)
  558.     update_last_new(last_new_gh);
  559.  
  560.     if (!use_selections) return;
  561.  
  562.     sel = open_file(sel_path, OPEN_READ);
  563.     if (sel == NULL) return;
  564.  
  565.     while (fgets(selbuf, RC_LINE_MAX, sel) != NULL) {
  566.     for (bp = selbuf; (c = *bp); bp++)
  567.         if (c == SP || c == SEL_NEW) break;
  568.  
  569.     if (c == NUL) continue;
  570.     *bp = NUL;
  571.     gh = lookup_no_alias(selbuf);
  572.     if (gh == NULL || gh->master_flag & M_ALIASED) continue;
  573.     *bp = c;
  574.     if (c == SEL_NEW) gh->group_flag |= G_NEW;
  575.     gh->select_orig = gh->select_line = copy_str(selbuf);
  576.     }
  577.     fclose(sel);
  578. }
  579.  
  580. /*
  581.  * prepare to use newsrc & select information for a specific group
  582.  */
  583.  
  584. static struct rc_info {
  585.     char *rc_p;            /* pointer into newsrc_line */
  586.     article_number rc_min;    /* current newsrc range min */
  587.     article_number rc_max;    /* current newsrc range max */
  588.     char rc_delim;        /* delimiter character */
  589.  
  590.     char *sel_p;        /* pointer into select_line */
  591.     char *sel_initp;        /* rc_p after initialization */
  592.     article_number sel_min;    /* current select range min */
  593.     article_number sel_max;    /* current select range max */
  594.     article_number sel_digest;    /* current digest */
  595.     attr_type sel_type;        /* current select range type */
  596.     char sel_delim;        /* delimiter character */
  597. } orig, cur;
  598.  
  599. static int rctest_mode;
  600.  
  601. static void
  602. init_rctest(gh, r)
  603. group_header *gh;
  604. register struct rc_info *r;
  605. {
  606.     if (r->rc_p == NULL) {
  607.     r->rc_min = r->rc_max = END_OF_LIST;
  608.     } else {
  609.     r->rc_min = r->rc_max = -1;
  610.     r->rc_delim = SP;
  611.     r->rc_p += gh->group_name_length + 1;
  612.     }
  613.  
  614.     r->sel_digest = 0;
  615.     if (r->sel_p == NULL) {
  616.     r->sel_min = r->sel_max = END_OF_LIST;
  617.     } else {
  618.     r->sel_p += gh->group_name_length + 1;
  619.     r->sel_min = r->sel_max = -1;
  620.     r->sel_delim = SP;
  621.     }
  622. }
  623.  
  624. void
  625. use_newsrc(gh, mode)
  626. register group_header *gh;
  627. int mode;
  628. {
  629.     orig.rc_p = gh->newsrc_orig;
  630.     orig.sel_p = gh->select_orig;
  631.     cur.rc_p = gh->newsrc_line;
  632.     cur.sel_p = gh->select_line;
  633.  
  634.     rctest_mode = mode;
  635.  
  636.     switch (mode) {
  637.      case 0:
  638.     init_rctest(gh, &cur);
  639.     break;
  640.      case 1:
  641.     init_rctest(gh, &orig);
  642.     break;
  643.      case 2:
  644.     init_rctest(gh, &cur);
  645.     init_rctest(gh, &orig);
  646.     break;
  647.     }
  648. }
  649.  
  650. /*
  651. #define TRC(wh)  TR( ("r%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, rc_min, rc_max, rc_delim) )
  652. #define TSEL(wh) TR( ("s%d>%-8.8s< %ld %ld %ld %c\n", wh, p ? p : "***", n, sel_min, sel_max, sel_delim) )
  653. */
  654. #define TRC(wh)
  655. #define TSEL(wh)
  656.  
  657. static attr_type rctest(ah, r)
  658. register article_header *ah;
  659. register struct rc_info *r;
  660. {
  661.     register char *p;
  662.     register int c;
  663.     register int32 n = ah->a_number, x;
  664.  
  665.     while (n > r->rc_max) {
  666.     /* get next interval from newsrc line */
  667.     r->rc_min = -1;
  668.     x = 0;
  669.     p = r->rc_p;
  670.     TRC(1);
  671.  
  672.     if (*p == RC_DELIM) p++;
  673.     if (*p == NUL || *p == NL)
  674.         r->rc_min = r->rc_max = END_OF_LIST;
  675.     else {
  676.         for ( ; (c = *p) && c != RC_DELIM && c != NL; p++) {
  677.         if (c == RC_RANGE) {
  678.             if (r->rc_min < 0)
  679.             r->rc_min = x;
  680.             else
  681.             msg("syntax error in rc file");
  682.             x = 0;
  683.             continue;
  684.         }
  685.  
  686.         if (isascii(*p) && isdigit(*p))
  687.             x = x*10 + c - '0';
  688.         }
  689.         r->rc_max = x;
  690.         if (r->rc_min < 0) r->rc_min = x;
  691.         r->rc_p = p;
  692.     }
  693.     }
  694.     TRC(2);
  695.  
  696.     if (n >= r->rc_min && n <= r->rc_max) return A_READ;
  697.  
  698.     p = r->sel_p;
  699.     if (r->sel_digest != 0) {
  700.     if (n == r->sel_digest && (ah->flag & A_DIGEST)) {
  701.         if (*(r->sel_p) == SEL_END_DIGEST) return A_READ;
  702.         n = ah->fpos;
  703.     } else {
  704.         if (n < r->sel_digest) return 0;
  705.         while (*p && *p++ != SEL_END_DIGEST);
  706.         r->sel_digest = 0;
  707.         r->sel_min = r->sel_max = -1;
  708.     }
  709.     }
  710.  
  711.     while (n > r->sel_max) {
  712.     r->sel_min = -1;
  713.     r->sel_type = A_SELECT;
  714.     x = 0;
  715.     TSEL(3);
  716.  
  717.     for (;;) {
  718.         switch (*p) {
  719.          case SEL_SELECT:
  720.         r->sel_type = A_SELECT;
  721.         p++;
  722.         continue;
  723.          case SEL_LEAVE:
  724.         r->sel_type = A_LEAVE;
  725.         p++;
  726.         continue;
  727.          case SEL_SEEN:
  728.         r->sel_type = A_SEEN;
  729.         p++;
  730.         continue;
  731.          case SEL_UNREAD:
  732.         r->sel_type = 0;
  733.         p++;
  734.         continue;
  735.          case SEL_DIGEST:
  736.         while (*p && *p++ != SEL_END_DIGEST);
  737.         continue;
  738.          case SEL_END_DIGEST:
  739.         if (r->sel_digest) {
  740.             if (r->sel_digest == ah->a_number) {
  741.             r->sel_p = p;
  742.             return A_READ;
  743.             }
  744.             r->sel_digest = 0;
  745.         }
  746.         p++;
  747.         r->sel_type = A_SELECT;
  748.         continue;
  749.          default:
  750.         break;
  751.         }
  752.         break;
  753.     }
  754.  
  755.     if (*p == NUL || *p == NL) {
  756.         r->sel_min = r->sel_max = END_OF_LIST;
  757.         break;
  758.     }
  759.  
  760.     for ( ; (c = *p) ; p++ ) {
  761.         switch (c) {
  762.          case '0':
  763.          case '1':
  764.          case '2':
  765.          case '3':
  766.          case '4':
  767.          case '5':
  768.          case '6':
  769.          case '7':
  770.          case '8':
  771.          case '9':
  772.         x = x*10 + c - '0';
  773.         continue;
  774.  
  775.          case SEL_SELECT:
  776.          case SEL_LEAVE:
  777.          case SEL_SEEN:
  778.          case SEL_UNREAD:
  779.         break;
  780.  
  781.          case SEL_RANGE:
  782.         if (r->sel_min < 0)
  783.             r->sel_min = x;
  784.         else
  785.             msg("syntax error in sel file");
  786.         x = 0;
  787.         continue;
  788.  
  789.          case SEL_DIGEST:
  790.         n = ah->a_number;
  791.         if (n > x) {
  792.             while (*p && (*p++ != SEL_END_DIGEST));
  793.             x = -1;
  794.             break;
  795.         }
  796.         p++;
  797.         r->sel_digest = x;
  798.         if (n < r->sel_digest) {
  799.             r->sel_p = p;
  800.             return 0;
  801.         }
  802.         n = ah->fpos;
  803.         x = -1;
  804.         break;
  805.  
  806.          case NL:
  807.         if (r->sel_digest == 0) break;
  808.         /*FALLTHRU*/
  809.          case SEL_END_DIGEST:
  810.         if (r->sel_digest == ah->a_number) {
  811.             r->sel_p = p;
  812.             return (ah->fpos == x) ? r->sel_type : A_READ;
  813.         }
  814.         r->sel_digest = 0;
  815.         x = -1;
  816.         break;
  817.         }
  818.         break;
  819.     }
  820.     r->sel_max = x;
  821.     if (r->sel_min < 0) r->sel_min = x;
  822.     r->sel_p = p;
  823.     }
  824.  
  825.     if (n >= r->sel_min && n <= r->sel_max) return r->sel_type;
  826.  
  827.     if (r->sel_digest) return A_READ; /* only read articles are not listed */
  828.  
  829.     return 0;    /* unread, unseen, unselected */
  830. }
  831.  
  832. attr_type test_article(ah)
  833. article_header *ah;
  834. {
  835.     attr_type a;
  836.  
  837.     switch (rctest_mode) {
  838.      case 0:
  839.     return rctest(ah, &cur);
  840.      case 1:
  841.     return rctest(ah, &orig);
  842.      case 2:
  843.     a = rctest(ah, &cur);
  844.     if (a != A_READ) return a;
  845.     if (rctest(ah, &orig) == A_READ) return A_KILL;
  846.     return a;
  847.     }
  848.     return 0;
  849. }
  850.  
  851. /*
  852.  * We only mark the articles that should remain unread
  853.  */
  854. static char *rc_p;        /* pointer into newsrc_line */
  855. static article_number rc_min;    /* current newsrc range min */
  856. static char rc_delim;        /* delimiter character */
  857.  
  858. static char *sel_p;        /* pointer into select_line */
  859. static char *sel_initp;        /* rc_p after initialization */
  860. static article_number sel_min;    /* current select range min */
  861. static article_number sel_max;    /* current select range max */
  862. static article_number sel_digest; /* current digest */
  863. static char sel_delim;        /* delimiter character */
  864.  
  865. /*VARARGS*/
  866. static void
  867. append(va_alist)
  868. va_dcl
  869. {
  870.     int x;
  871.     register char *p;
  872.     char **pp, *fmt;
  873.     use_vararg;
  874.  
  875.     start_vararg;
  876.     x = va_arg1(int);
  877.     pp = x ? &sel_p : &rc_p;
  878.     p = *pp;
  879.     if (p > (x ? &selbuf[RC_LINE_MAX - 16] : &rcbuf[RC_LINE_MAX - 16])) {
  880.     msg("%s line too long", x ? "select" : ".newsrc");
  881.     end_vararg;
  882.     return;
  883.     }
  884.     fmt = va_arg2(char *);
  885.     vsprintf(p, fmt, va_args3toN);
  886.     end_vararg;
  887.  
  888.     while (*p) p++;
  889.     *p = NL;
  890.     p[1] = NUL;
  891.     *pp = p;
  892. }
  893.  
  894. static void
  895. append_range(pp, delim, rmin, rmax)
  896. int pp;
  897. char delim;
  898. article_number rmin, rmax;
  899. {
  900.     if (rmin == rmax)
  901.     append(pp, "%c%ld", delim, (long)rmin);
  902.     else
  903.     append(pp, "%c%ld%c%ld", delim, (long)rmin, RC_RANGE, (long)rmax);
  904. }
  905.  
  906. static int32 mark_counter;
  907.  
  908. static void
  909. begin_rc_update(gh)
  910. register group_header *gh;
  911. {
  912.     add_unread(gh, -1);
  913.     mark_counter = 0;
  914.  
  915.     rc_p = rcbuf;
  916.     rc_min = 1;
  917.     append(0, "%s%c", gh->group_name,
  918.        gh->group_flag & G_UNSUBSCRIBED ? RC_UNSUBSCR : RC_SUBSCR);
  919.     rc_delim = SP;
  920.     sel_p = selbuf;
  921.     sel_min = 0;
  922.     sel_max = 0;
  923.     sel_digest = 0;
  924.     sel_delim = SP;
  925.     append(1, "%s%c", gh->group_name,
  926.        (gh->group_flag & G_NEW) ? SEL_NEW : SP);
  927.     /* sel_initp == sep_p => empty list */
  928.     sel_initp = (gh->group_flag & G_NEW) ? NULL : sel_p;
  929. }
  930.  
  931. static void
  932. end_rc_update(gh)
  933. register group_header *gh;
  934. {
  935.     if (rc_min <= gh->last_db_article)
  936.     append_range(0, rc_delim, rc_min, gh->last_db_article);
  937.  
  938.     if (gh->newsrc_line != NULL && strcmp(rcbuf, gh->newsrc_line)) {
  939.     if (gh->newsrc_orig != gh->newsrc_line)
  940.         freeobj(gh->newsrc_line);
  941.     gh->newsrc_line = NULL;
  942.     }
  943.  
  944.     if (gh->newsrc_line == NULL) {
  945.     gh->newsrc_line = copy_str(rcbuf);
  946.     dump_newsrc();
  947.     }
  948.  
  949.     if (sel_digest)
  950.     append(1, "%c", SEL_END_DIGEST);
  951.     else
  952.     if (sel_min)
  953.         append_range(1, sel_delim, sel_min, sel_max);
  954.  
  955.     if (gh->select_line) {
  956.     if (strcmp(selbuf, gh->select_line) == 0) goto out;
  957.     } else
  958.         if (sel_p == sel_initp) goto out;
  959.  
  960.     if (gh->select_line && gh->select_orig != gh->select_line)
  961.     freeobj(gh->select_line);
  962.  
  963.     gh->select_line = (sel_p == sel_initp) ? NULL : copy_str(selbuf);
  964.     dump_select();
  965.  
  966.  out:
  967.     if ((gh->last_article = get_last_article(gh)) < 0)
  968.     gh->last_article = 0;
  969.     if (gh->last_article < gh->first_article)
  970.     gh->first_article = gh->last_article;
  971.  
  972.     gh->group_flag |= G_READ;    /* should not call update_group again */
  973.     if (mark_counter > 0) {
  974.     gh->unread_count = mark_counter;
  975.     add_unread(gh, 0);
  976.     }
  977. }
  978.  
  979. static void
  980. mark_article(ah, how)
  981. register article_header *ah;
  982. attr_type how;
  983. {
  984.     register article_number anum;
  985.     char delim = 0;
  986.  
  987.     switch (how) {
  988.      case A_SELECT:
  989.     delim = SEL_SELECT;
  990.     break;
  991.      case A_SEEN:
  992.     delim = SEL_SEEN;
  993.     break;
  994.      case A_LEAVE:
  995.      case A_LEAVE_NEXT:
  996.     delim = SEL_LEAVE;
  997.     break;
  998.      case 0:
  999.     delim = SEL_UNREAD;
  1000.     break;
  1001.     }
  1002.  
  1003.     mark_counter++;
  1004.     anum = ah->a_number;
  1005.  
  1006.     if (rc_min < anum) {
  1007.     append_range(0, rc_delim, rc_min, anum - 1);
  1008.     rc_delim = RC_DELIM;
  1009.  
  1010.     if ((ah->flag & A_DIGEST) == 0
  1011.         && sel_min && delim == sel_delim && sel_max == (rc_min - 1))
  1012.         sel_max = anum - 1;    /* expand select range over read articles */
  1013.     }
  1014.     rc_min = anum + 1;
  1015.  
  1016.     if (ah->flag & A_DIGEST) {
  1017.     if (sel_digest != anum) {
  1018.         if (sel_digest) {
  1019.         append(1, "%c", SEL_END_DIGEST);
  1020.         } else
  1021.         if (sel_min) {
  1022.             append_range(1, sel_delim, sel_min, sel_max);
  1023.             sel_min = 0;
  1024.         }
  1025.         append(1, "%c%ld%c", SEL_SELECT, (long)anum, SEL_DIGEST);
  1026.         sel_digest = anum;
  1027.     }
  1028.  
  1029.     append(1, "%c%ld", delim, (long)ah->fpos);
  1030.     return;
  1031.     }
  1032.  
  1033.     if (sel_digest) {
  1034.     append(1, "%c", SEL_END_DIGEST);
  1035.     sel_digest = 0;
  1036.     }
  1037.  
  1038.     if (sel_min) {
  1039.     if (delim != sel_delim || delim == SEL_UNREAD) {
  1040.         append_range(1, sel_delim, sel_min, sel_max);
  1041.         sel_delim = delim;
  1042.         if (delim == SEL_UNREAD)
  1043.         sel_min = 0;
  1044.         else
  1045.         sel_min = anum;
  1046.     } else
  1047.         sel_max = anum;
  1048.     } else
  1049.     if (delim != SEL_UNREAD) {
  1050.         sel_min = sel_max = anum;
  1051.         sel_delim = delim;
  1052.     }
  1053. }
  1054.  
  1055. void
  1056. flush_newsrc()
  1057. {
  1058.     newsrc_update_freq = 0;
  1059.     if (select_update_count) dump_select();
  1060.     if (newsrc_update_count) dump_newsrc();
  1061. }
  1062.  
  1063. int
  1064. restore_bak()
  1065. {
  1066.     if (no_update)
  1067.     return 1;
  1068.  
  1069.     prompt("Are you sure? ");
  1070.     if (!yes(1)) return 0;
  1071.  
  1072.     if (dump_file(newsrc_file, DM_ORIG_NEWSRC))
  1073.     nn_exitmsg(1, "Could not restore %s -- restore %s file manually",
  1074.            newsrc_file, bak_suffix);
  1075.  
  1076.     prompt("Restore selections? ");
  1077.     if (yes(1)) dump_file(sel_path, DM_ORIG_SELECT);
  1078.  
  1079.     no_update = 1;    /* so current group is not updated */
  1080.     return 1;
  1081. }
  1082.  
  1083. /*
  1084.  *    Update .newsrc for one group.
  1085.  *    sort_articles(-2) MUST HAVE BEEN CALLED BEFORE USE.
  1086.  */
  1087.  
  1088. export int rc_merged_groups_hack = 0;
  1089.  
  1090. void
  1091. update_rc(gh)
  1092. register group_header *gh;
  1093. {
  1094.     register article_header *ah, **ahp;
  1095.     register article_number art;
  1096.     static int junk_seen = 0;
  1097.  
  1098.     if (!rc_merged_groups_hack) junk_seen = 0;
  1099.  
  1100.     if (gh->group_flag & (G_FOLDER | G_FAKED)) return;
  1101.  
  1102.     begin_rc_update(gh);
  1103.  
  1104.     for (ahp = articles, art = 0; art < n_articles; ahp++, art++) {
  1105.     ah = *ahp;
  1106.     if (ah->a_group != NULL && ah->a_group != gh) continue;
  1107.  
  1108.     switch (ah->attr) {
  1109.      case A_READ:
  1110.      case A_KILL:
  1111.         continue;
  1112.  
  1113.      case A_LEAVE:
  1114.      case A_LEAVE_NEXT:
  1115.      case A_SELECT:
  1116.         mark_article(ah, ah->attr);
  1117.         continue;
  1118.  
  1119.      case A_SEEN:
  1120.         if (junk_seen == 0) {
  1121.         junk_seen = -1;
  1122.         if (auto_junk_seen) {
  1123.             if (conf_junk_seen) {
  1124.             prompt("\1Junk seen articles\1 ");
  1125.             if (yes(0) > 0) junk_seen = 1;
  1126.             } else
  1127.             junk_seen = 1;
  1128.         }
  1129.         }
  1130.         if (junk_seen > 0) continue;
  1131.         mark_article(ah, (attr_type)(retain_seen_status ? A_SEEN : 0));
  1132.         continue;
  1133.  
  1134.      case A_AUTO_SELECT:
  1135.      default:
  1136.         mark_article(ah, (attr_type)0);
  1137.         continue;
  1138.     }
  1139.     }
  1140.  
  1141.     end_rc_update(gh);
  1142. }
  1143.  
  1144. void
  1145. update_rc_all(gh, unsub)
  1146. register group_header *gh;
  1147. int unsub;
  1148. {
  1149.     if (unsub) {
  1150.     gh->group_flag &= ~G_NEW;
  1151.     gh->group_flag |= G_UNSUBSCRIBED;
  1152.  
  1153.     if (!keep_unsubscribed) {
  1154.         add_unread(gh, -1);
  1155.         if (gh->newsrc_line != NULL && gh->newsrc_orig != gh->newsrc_line)
  1156.         freeobj(gh->newsrc_line);
  1157.         gh->newsrc_line = NULL;
  1158.         if (gh->newsrc_orig != NULL) dump_newsrc();
  1159.         if (gh->select_line != NULL && gh->newsrc_orig != gh->select_line)
  1160.         freeobj(gh->select_line);
  1161.         gh->select_line = NULL;
  1162.         if (gh->select_orig != NULL) dump_select();
  1163.         return;
  1164.     }
  1165.     
  1166.     if (keep_unsub_long) {
  1167.         update_rc(gh);
  1168.         return;
  1169.     }
  1170.     }
  1171.  
  1172.     begin_rc_update(gh);
  1173.     end_rc_update(gh);
  1174. }
  1175.  
  1176. void
  1177. add_to_newsrc(gh)
  1178. group_header *gh;
  1179. {
  1180.     gh->group_flag &= ~G_UNSUBSCRIBED;
  1181.  
  1182.     if (gh->newsrc_seq != NULL || gh == rc_seq_tail) {
  1183.     update_rc(gh);
  1184.     return;
  1185.     }
  1186.     
  1187.     rc_seq_tail->newsrc_seq = gh;
  1188.     rc_seq_tail = gh;
  1189.     if (gh->last_db_article > 0)
  1190.     sprintf(rcbuf, "%s: %s%ld\n", gh->group_name,
  1191.         gh->last_db_article > 1 ? "1-" : "",
  1192.         (long)gh->last_db_article);
  1193.     else
  1194.     sprintf(rcbuf, "%s:\n", gh->group_name);
  1195.     gh->newsrc_line = copy_str(rcbuf);
  1196.     dump_newsrc();
  1197. }
  1198.  
  1199. int32 restore_rc(gh, last)
  1200. register group_header *gh;
  1201. article_number last;
  1202. {
  1203.     register article_number *numtab, n;
  1204.     register attr_type *attrtab, attr;
  1205.     register int32 at, atmax;
  1206.     article_header ahdr;
  1207.     int32 count;
  1208.  
  1209.     if (last > gh->last_db_article) return 0;
  1210.  
  1211.     if (gh->unread_count <= 0) {
  1212.     /* no unread articles to account for -- quick update */
  1213.     n = gh->last_db_article;    /* fake for end_rc_update */
  1214.     gh->last_db_article = last;
  1215.     begin_rc_update(gh);
  1216.     end_rc_update(gh);
  1217.     gh->last_db_article = n;
  1218.     add_unread(gh, 1); /* not done by end_rc_update bec. mark_counter==0 */
  1219.     return gh->unread_count;
  1220.     }
  1221.  
  1222.     /* there are unread articles in the group */
  1223.     /* we must truncate rc&select lines to retain older unread articles */
  1224.  
  1225.     atmax = at = 0;
  1226.     numtab = NULL;
  1227.     attrtab = NULL;
  1228.     
  1229.     use_newsrc(gh, 0);
  1230.     ahdr.flag = 0;
  1231.     count = gh->unread_count;
  1232.  
  1233.     for (n = gh->last_article + 1; n <= last; n++) {
  1234.     if (cur.rc_min == END_OF_LIST) {
  1235.         /* current & rest is unread */
  1236.         last = n - 1;
  1237.         break;
  1238.     }
  1239.     ahdr.a_number = n;
  1240.     if ((attr = rctest(&ahdr, &cur)) == A_READ) continue;
  1241.     if (at >= atmax) {
  1242.         atmax += 100;
  1243.         numtab = resizeobj(numtab, article_number, atmax);
  1244.         attrtab = resizeobj(attrtab, attr_type, atmax);
  1245.     }
  1246.     numtab[at] = n;
  1247.     attrtab[at] = attr;
  1248.     at++;
  1249.     }
  1250.  
  1251.     begin_rc_update(gh);
  1252.     while (--at >= 0) {
  1253.     ahdr.a_number = *numtab++;
  1254.     mark_article(&ahdr, *attrtab++);
  1255.     }
  1256.     for (n = last+1; n <= gh->last_db_article; n++) {
  1257.     ahdr.a_number = n;
  1258.     mark_article(&ahdr, (attr_type)0);
  1259.     }
  1260.     end_rc_update(gh);
  1261.     return gh->unread_count - count;
  1262. }
  1263.  
  1264. int
  1265. restore_unread(gh)
  1266. register group_header *gh;
  1267. {
  1268.     if (gh->select_line != gh->select_orig) {
  1269.     if (gh->select_line != NULL) freeobj(gh->select_line);
  1270.     gh->select_line = gh->select_orig;
  1271.     dump_select();
  1272.     }
  1273.  
  1274.     if (gh->newsrc_orig == gh->newsrc_line) return 0;
  1275.  
  1276.     add_unread(gh, -1);
  1277.     if (gh->newsrc_line != NULL) freeobj(gh->newsrc_line);
  1278.     gh->newsrc_line = gh->newsrc_orig;
  1279.     gh->last_article = gh->first_article;
  1280.     dump_newsrc();
  1281.  
  1282.     add_unread(gh, 1);
  1283.  
  1284.     return 1;
  1285. }
  1286.  
  1287. void
  1288. count_unread_articles()
  1289. {
  1290.     register group_header *gh;
  1291.     long n = 0;
  1292.  
  1293.     unread_articles = 0;
  1294.     unread_groups = 0;
  1295.  
  1296.     Loop_Groups_Sequence(gh) {
  1297.     gh->unread_count = 0;
  1298.  
  1299.     if (gh->master_flag & M_NO_DIRECTORY) continue;
  1300.  
  1301.     if (gh->last_db_article > gh->last_article) {
  1302.         n = unread_articles;
  1303.         add_unread(gh, 1);
  1304.     }
  1305.     
  1306.     if ((gh->group_flag & G_COUNTED) == 0) continue;
  1307.     if (verbose)
  1308.         tprintf("%6d %s\n", unread_articles - n, gh->group_name);
  1309.     }
  1310. }
  1311.  
  1312.  
  1313. void
  1314. prt_unread(format)
  1315. register char *format;
  1316. {
  1317.     if (format == NULL) {
  1318.     tprintf("No News (is good news)\n");
  1319.     return;
  1320.     }
  1321.  
  1322.     while (*format) {
  1323.     if (*format != '%') {
  1324.         tputc(*format++);
  1325.         continue;
  1326.     }
  1327.     format++;
  1328.     switch (*format++) {
  1329.      case 'u':
  1330.         tprintf("%ld unread article%s", unread_articles, plural((long)unread_articles));
  1331.         continue;
  1332.      case 'g':
  1333.         tprintf("%d group%s", unread_groups, plural((long)unread_groups));
  1334.         continue;
  1335.      case 'i':
  1336.         tprintf(unread_articles == 1 ? "is" : "are");
  1337.         continue;
  1338.      case 'U':
  1339.         tprintf("%ld", unread_articles);
  1340.         continue;
  1341.      case 'G':
  1342.         tprintf("%d", unread_groups);
  1343.         continue;
  1344.     }
  1345.     }
  1346. }
  1347.  
  1348. int
  1349. add_unread(gh, mode)
  1350. group_header *gh;
  1351. int mode;    /* +1 => count + add, 0 => gh->unread_count, -1 => subtract */
  1352. {
  1353.     int32 old_count;
  1354.     article_header ahdr;
  1355.  
  1356.     old_count = gh->unread_count;
  1357.  
  1358.     if (mode == 0) goto add_directly;
  1359.  
  1360.     if (gh->group_flag & G_COUNTED) {
  1361.     unread_articles -= gh->unread_count;
  1362.     unread_groups --;
  1363.     gh->unread_count = 0;
  1364.     gh->group_flag &= ~G_COUNTED;
  1365.     }
  1366.  
  1367.     if (mode < 0) goto out;
  1368.  
  1369.     if (quick_unread_count)
  1370.     gh->unread_count = gh->last_db_article - gh->last_article;
  1371.     else {
  1372.     use_newsrc(gh, 0);
  1373.     ahdr.flag = 0;
  1374.     for (ahdr.a_number = gh->last_article + 1;
  1375.          ahdr.a_number <= gh->last_db_article;
  1376.          ahdr.a_number++) {
  1377.         if (cur.rc_min == END_OF_LIST) {
  1378.         gh->unread_count += gh->last_db_article - ahdr.a_number + 1;
  1379.         break;
  1380.         }
  1381.         if (rctest(&ahdr, &cur) != A_READ)
  1382.         gh->unread_count++;
  1383.     }
  1384.     }
  1385.  
  1386.  add_directly:
  1387.     if (gh->unread_count <= 0) {
  1388.     gh->unread_count = 0;
  1389.     goto out;
  1390.     }
  1391.  
  1392.     if (gh->group_flag & G_UNSUBSCRIBED) goto out;
  1393.  
  1394.     unread_articles += gh->unread_count;
  1395.     unread_groups++;
  1396.     gh->group_flag |= G_COUNTED;
  1397.     
  1398.  out:
  1399.     return old_count != gh->unread_count;
  1400. }
  1401.  
  1402. /*
  1403.  *    nngrep
  1404.  */
  1405.  
  1406. static int
  1407.     grep_all = 0,
  1408.     grep_new = 0,
  1409.     grep_not_sequence = 0,
  1410.     grep_pending = 0,
  1411.     grep_read = 0,
  1412.     grep_sequence = 0,
  1413.     grep_unsub = 0,
  1414.     grep_long = 0,
  1415.     grep_patterns;
  1416.  
  1417. Option_Description(grep_options) {
  1418.     'a', Bool_Option(grep_all),
  1419.     'i', Bool_Option(grep_not_sequence),
  1420.     'n', Bool_Option(grep_new),
  1421.     'p', Bool_Option(grep_pending),
  1422.     'r', Bool_Option(grep_read),
  1423.     's', Bool_Option(grep_sequence),
  1424.     'u', Bool_Option(grep_unsub),
  1425.     'l', Bool_Option(grep_long),
  1426.     '\0',
  1427. };
  1428.  
  1429. void
  1430. opt_nngrep(argc, argv)
  1431. int argc;
  1432. char *argv[];
  1433. {
  1434.     grep_patterns =
  1435.     parse_options(argc, argv, (char *)NULL, grep_options, " pattern...");
  1436. }
  1437.  
  1438. void
  1439. do_grep(pat)
  1440. char **pat;
  1441. {
  1442.     register group_header *gh;
  1443.     register regexp **re = NULL;
  1444.     register int i;
  1445.     int header = 1;
  1446.  
  1447.     if (grep_patterns > 0) {
  1448.     re = newobj(regexp *, grep_patterns);
  1449.     for (i = 0; i < grep_patterns; i++)
  1450.         re[i] = regcomp(pat[i]);
  1451.     }
  1452.  
  1453.     Loop_Groups_Sorted(gh) {
  1454.     if (gh->master_flag & M_IGNORE_GROUP) continue;
  1455.  
  1456.     if (grep_pending && gh->unread_count <= 0) continue;
  1457.     if (grep_read && gh->unread_count > 0) continue;
  1458.     if (grep_sequence && (gh->group_flag & G_SEQUENCE) == 0) continue;
  1459.     if (grep_not_sequence && (gh->group_flag & G_SEQUENCE)) continue;
  1460.     if (grep_new && (gh->group_flag & G_NEW) == 0) continue;
  1461.     if (!grep_all) {
  1462.         if (grep_unsub && (gh->group_flag & G_UNSUBSCRIBED) == 0) continue;
  1463.         if (!grep_unsub && (gh->group_flag & G_UNSUBSCRIBED)) continue;
  1464.     }
  1465.  
  1466.     if (grep_patterns > 0) {
  1467.         for (i = 0; i < grep_patterns; i++)
  1468.         if (regexec(re[i], gh->group_name)) break;
  1469.         if (i == grep_patterns) continue;
  1470.     }
  1471.  
  1472.     if (grep_long) {
  1473.         if (header)
  1474.         tprintf("SUBSCR IN_RC NEW UNREAD SEQUENCE GROUP\n");
  1475.         header = 0;
  1476.  
  1477.         tprintf(" %s   %s   %s ",
  1478.            (gh->group_flag & G_UNSUBSCRIBED) ? "no " : "yes",
  1479.            (gh->newsrc_line == NULL) ? "no " : "yes",
  1480.            (gh->group_flag & G_NEW) ? "yes" : "no ");
  1481.  
  1482.         if (gh->unread_count > 0)
  1483.         tprintf("%6d ", gh->unread_count);
  1484.         else
  1485.         tprintf("       ");
  1486.         if (gh->group_flag & G_SEQUENCE)
  1487.         tprintf("  %4d   ", gh->preseq_index);
  1488.         else
  1489.         tprintf("         ");
  1490.     }
  1491.  
  1492.     tprintf("%s\n", gh->group_name);
  1493.     }
  1494. }
  1495.  
  1496.  
  1497. /*
  1498.  *    nntidy
  1499.  */
  1500.  
  1501. static int
  1502.     tidy_unsubscribed = 0,    /* truncate lines for unsub groups*/
  1503.     tidy_remove_unsub = 0,    /* remove lines for unsub groups*/
  1504.     tidy_sequence = 0,        /* remove groups not in sequence */
  1505.     tidy_ignored = 0,        /* remove G_IGN groups */
  1506.     tidy_crap = 0,        /* remove unrecognized lines */
  1507.     tidy_all = 0;        /* all of the above */
  1508.  
  1509. Option_Description(tidy_options) {
  1510.     'N', Bool_Option(no_update),
  1511.     'Q', Bool_Option(silent),
  1512.     'v', Bool_Option(verbose),
  1513.     'a', Bool_Option(tidy_all),
  1514.     'c', Bool_Option(tidy_crap),
  1515.     'i', Bool_Option(tidy_ignored),
  1516.     'r', Bool_Option(tidy_remove_unsub),
  1517.     's', Bool_Option(tidy_sequence),
  1518.     'u', Bool_Option(tidy_unsubscribed),
  1519.     '\0',
  1520. };
  1521.  
  1522. int
  1523. opt_nntidy(argc, argv)
  1524. int argc;
  1525. char *argv[];
  1526. {
  1527.     return parse_options(argc, argv, (char *)NULL, 
  1528.              tidy_options, " [group]...");
  1529. }
  1530.  
  1531. void
  1532. do_tidy_newsrc()
  1533. {
  1534.     register group_header *gh;
  1535.     int changed;
  1536.     char *why;
  1537.  
  1538.     /* visit_rc_file has been called. */
  1539.  
  1540.     keep_rc_backup = 1;
  1541.     bak_suffix = ".tidy";
  1542.  
  1543.     tidy_newsrc = 0;
  1544.     changed = 0;
  1545.  
  1546.     if (tidy_all)
  1547.     tidy_sequence = tidy_ignored = tidy_crap = tidy_unsubscribed = 1;
  1548.  
  1549.     newsrc_update_freq = 9999;
  1550.  
  1551.     Loop_Groups_Newsrc(gh) {
  1552.     if ((gh->master_flag & M_VALID) == 0) {
  1553.         why = "Unknown group:   ";
  1554.         goto delete;
  1555.     }
  1556.     if (tidy_sequence && (gh->group_flag & G_SEQUENCE) == 0) {
  1557.         why = "Not in sequence: ";
  1558.         goto delete;
  1559.     }
  1560.     if (tidy_ignored && (gh->master_flag & M_IGNORE_GROUP)) {
  1561.         why = "Ignored group:   ";
  1562.         goto delete;
  1563.     }
  1564.     if (tidy_crap && (gh->group_flag & G_FAKED)) {
  1565.         why = "Crap in .newsrc: ";
  1566.         goto delete;
  1567.     }
  1568.     if (tidy_remove_unsub && (gh->group_flag & G_UNSUBSCRIBED)) {
  1569.         if (gh->group_flag & G_FAKED) continue;
  1570.         why = "Unsubscribed:    ";
  1571.         goto delete;
  1572.     }
  1573.  
  1574.     if (tidy_unsubscribed && (gh->group_flag & G_UNSUBSCRIBED)) {
  1575.         if (gh->group_flag & G_FAKED) continue;
  1576.  
  1577.         begin_rc_update(gh);
  1578.         gh->last_db_article = 0;
  1579.         end_rc_update(gh);
  1580.  
  1581.         if (gh->newsrc_line != gh->newsrc_orig) {
  1582.         why = "Truncated:       ";
  1583.         goto change;
  1584.         }
  1585.     }
  1586.     if (verbose) {
  1587.         why = "Ok:              ";
  1588.         goto report;
  1589.     }
  1590.     continue;
  1591.  
  1592.      delete:
  1593.     gh->newsrc_line = NULL;
  1594.     gh->select_line = NULL;
  1595.  
  1596.      change:
  1597.     changed = 1;
  1598.  
  1599.      report:
  1600.     if (!silent) tprintf("%s%s\n", why, gh->group_name);
  1601.     }
  1602.  
  1603.     if (no_update) {
  1604.     if (changed) tprintf("Files were NOT updated\n");
  1605.     return;
  1606.     }
  1607.  
  1608.     if (changed) {
  1609.     newsrc_update_freq = 0;
  1610.     dump_newsrc();
  1611.     dump_select();
  1612.     tprintf("NOTICE: Original files are saved with %s extention\n", bak_suffix);
  1613.     }
  1614. }
  1615.  
  1616. /*
  1617.  *    nngoback
  1618.  */
  1619.  
  1620. static int
  1621.     goback_interact = 0, /* interactive nngoback */
  1622.     goback_days = -1,
  1623.     goback_alsounsub = 0; /* unsubscribed groups also */
  1624.  
  1625. Option_Description(goback_options) {
  1626.     'N', Bool_Option(no_update),
  1627.     'Q', Bool_Option(silent),
  1628.     'd', Int_Option(goback_days),
  1629.     'i', Bool_Option(goback_interact),
  1630.     'u', Bool_Option(goback_alsounsub),
  1631.     'v', Bool_Option(verbose),
  1632.     '\0',
  1633. };
  1634.  
  1635. int
  1636. opt_nngoback(argc, argvp)
  1637. int argc;
  1638. char ***argvp;
  1639. {
  1640.     int n;
  1641.     
  1642.     n = parse_options(argc, *argvp, (char *)NULL, goback_options,
  1643.               " days [groups]...");
  1644.  
  1645.     if (goback_days < 0) {
  1646.     if (n == 0 || !isdigit((*argvp)[1][0])) {
  1647.         fprintf(stderr, "usage: %s [-NQvi] days [groups]...\n", pname);
  1648.         nn_exit(1);
  1649.     }
  1650.     goback_days = atoi((*argvp)[1]);
  1651.     n--;
  1652.     ++*argvp;
  1653.     }
  1654.     return n;
  1655. }
  1656.  
  1657. void
  1658. do_goback()
  1659. {
  1660.     char back_act[FILENAME];
  1661.     FILE *ba;
  1662.     register group_header *gh;
  1663.     int32 count, total;
  1664.     int groups, y;
  1665.     
  1666.     sprintf(back_act, "%s/active.%d", db_directory, goback_days);
  1667.     if ((ba = open_file(back_act, OPEN_READ)) == NULL) {
  1668.     fprintf(stderr, "Cannot go back %d days\n", goback_days);
  1669.     nn_exit(1);
  1670.     }
  1671.  
  1672.     read_active_file(ba, (FILE *)NULL);
  1673.  
  1674.     fclose(ba);
  1675.  
  1676.     /* visit_rc_file has been called. */
  1677.  
  1678.     keep_rc_backup = 1;
  1679.     bak_suffix = ".goback";
  1680.     newsrc_update_freq = 9999;
  1681.     quick_unread_count = 0;
  1682.     total = groups = 0;
  1683.  
  1684.     if (goback_interact) {
  1685.     if (no_update) tprintf("Warning: changes will not be saved\n");
  1686.     init_term(1);
  1687.     nn_raw();
  1688.     }
  1689.  
  1690.     Loop_Groups_Sequence(gh) {
  1691.     if ((gh->master_flag & M_VALID) == 0) continue;
  1692.     if (!goback_alsounsub && (gh->group_flag & G_UNSUBSCRIBED)) continue;
  1693.  
  1694.     add_unread(gh, 1);
  1695.  
  1696.     count = restore_rc(gh, gh->last_a_article);
  1697.     if (count > 0) {
  1698.         if (goback_interact) {
  1699.         tprintf("%s + %ld ?  (y) ", gh->group_name, (long)count); fl;
  1700.         y = yes(0);
  1701.         tputc(CR); tputc(NL);
  1702.         switch (y) {
  1703.          case 1:
  1704.             break;
  1705.          case 0:
  1706.             gh->newsrc_line = gh->newsrc_orig;
  1707.             gh->select_line = gh->select_orig;
  1708.             continue;
  1709.          case -1:
  1710.             if (total > 0) {
  1711.             tprintf("\nSave changes sofar? (n) "); fl;
  1712.             if (yes(1) <= 0) nn_exit(0);
  1713.             }
  1714.             goto out;
  1715.         }
  1716.         } else
  1717.         if (verbose)
  1718.             tprintf("%5ld\t%s\n", (long)count, gh->group_name);
  1719.  
  1720.         total += count;
  1721.         groups++;
  1722.     }
  1723.     }
  1724.  
  1725.  out:
  1726.  
  1727.     if (goback_interact)
  1728.     unset_raw();
  1729.  
  1730.     if (total == 0) {
  1731.     tprintf("No articles marked\n");
  1732.     return;
  1733.     }
  1734.  
  1735.     flush_newsrc();
  1736.  
  1737.     if (verbose) tputc(NL);
  1738.     if (!silent)
  1739.     tprintf("%ld article%s marked unread in %d group%s\n",
  1740.            (long)total, plural((long)total),
  1741.            groups, plural((long)groups));
  1742.  
  1743.     if (no_update) tprintf("No files were updated\n");
  1744. }
  1745.  
  1746. /* fake this for read_active_file */
  1747.  
  1748. group_header *add_new_group(name)
  1749. char *name;
  1750. {
  1751.     return NULL;
  1752. }
  1753.