home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume27 / trn-3.3 / part06 / cache.c < prev    next >
C/C++ Source or Header  |  1993-11-27  |  18KB  |  784 lines

  1. /* $Id: cache.c,v 3.0 1992/02/01 03:09:32 davison Trn $
  2.  */
  3. /* This software is Copyright 1991 by Stan Barber. 
  4.  *
  5.  * Permission is hereby granted to copy, reproduce, redistribute or otherwise
  6.  * use this software as long as: there is no monetary profit gained
  7.  * specifically from the use or reproduction of this software, it is not
  8.  * sold, rented, traded or otherwise marketed, and this copyright notice is
  9.  * included prominently in any copy made. 
  10.  *
  11.  * The authors make no claims as to the fitness or correctness of this software
  12.  * for any use whatsoever, and it is provided as is. Any use of this software
  13.  * is at the user's own risk. 
  14.  */
  15.  
  16. #include "EXTERN.h"
  17. #include "common.h"
  18. #include "INTERN.h"
  19. #include "cache.h"
  20. #include "EXTERN.h"
  21. #include "intrp.h"
  22. #include "search.h"
  23. #include "ng.h"
  24. #include "trn.h"
  25. #include "ngdata.h"
  26. #include "term.h"
  27. #include "final.h"
  28. #include "artsrch.h"
  29. #include "head.h"
  30. #include "bits.h"
  31. #include "rcstuff.h"
  32. #include "hash.h"
  33. #include "rthread.h"
  34. #include "rt-ov.h"
  35. #include "rt-select.h"
  36. #include "rt-util.h"
  37. #include "util.h"
  38.  
  39. #ifdef PENDING
  40. #   ifdef ARTSEARCH
  41.     COMPEX srchcompex;        /* compiled regex for searchahead */
  42. #   endif
  43. #endif
  44.  
  45. HASHTABLE *subj_hash = 0;
  46.  
  47. int subject_cmp _((char *,int,HASHDATUM));
  48.  
  49. void
  50. cache_init()
  51. {
  52. #ifdef PENDING
  53. # ifdef ARTSEARCH
  54.     init_compex(&srchcompex)
  55. # endif
  56. #endif
  57.     ;
  58. }
  59.  
  60. NG_NUM cached_ng = -1;
  61. time_t cached_time = 0;
  62. ART_NUM cached_cnt = 0;
  63. ART_NUM cached_absfirst = 0;
  64.  
  65. void
  66. build_cache()
  67. {
  68.     if (cached_ng == ng && cached_absfirst == absfirst
  69.      && time((time_t*)NULL) < cached_time + 6*60*60L) {
  70.     grow_cache(lastart);
  71.     rc_to_bits();
  72.     if (sel_mode == SM_ARTICLE)
  73.         set_selector(sel_mode, sel_artsort);
  74.     else
  75.         set_selector(sel_threadmode, sel_threadsort);
  76.     thread_grow();
  77.     return;
  78.     }
  79.  
  80.     close_cache();
  81.  
  82.     cached_ng = ng;
  83.     cached_absfirst = absfirst;
  84.     cached_time = time((time_t*)NULL);
  85.     cached_cnt = lastart-absfirst+2 + 5;
  86.     article_list = (ARTICLE*)
  87.     safemalloc((MEM_SIZE)(cached_cnt * sizeof (ARTICLE)));
  88.     bzero((char*)article_list, cached_cnt * sizeof (ARTICLE));
  89.     subj_hash = hashcreate(201, subject_cmp);    /*TODO: pick a better size */
  90.  
  91.     rc_to_bits();            /* sets firstart */
  92.     first_cached = thread_always? absfirst : firstart;
  93.     last_cached = first_cached-1;
  94.     cached_all_in_range = FALSE;
  95. #ifdef PENDING
  96.     subj_to_get = xref_to_get = firstart;
  97. #endif
  98. #ifndef USE_NNTP
  99.     setmissingbits();
  100. #endif
  101.  
  102.     /* Cache as much data in advance as possible, possibly threading
  103.     ** articles as we go. */
  104.     thread_open();
  105. }
  106.  
  107. #define FIXPTR(p) (!(p) || (p)<old_list || ((p)-old_list) > (lastart-absfirst)\
  108.            ? (p) : (article_list + ((p)-old_list)))
  109.  
  110. void
  111. grow_cache(newlast)
  112. ART_NUM newlast;
  113. {
  114.     ART_NUM new_cnt = newlast-absfirst+2;
  115.  
  116.     if (new_cnt > cached_cnt) {
  117.     ARTICLE *old_list = article_list;
  118.     new_cnt += 5;
  119.     article_list = (ARTICLE*)saferealloc((char*)article_list,
  120.         (MEM_SIZE)(new_cnt * sizeof (ARTICLE)));
  121.     bzero((char*)(article_list+cached_cnt),
  122.         (new_cnt-cached_cnt) * sizeof (ARTICLE));
  123.     if (article_list != old_list) {
  124.         register ARTICLE *ap;
  125.         register SUBJECT *sp;
  126.         for (sp = first_subject; sp; sp = sp->next) {
  127.         sp->thread = FIXPTR(sp->thread);
  128.         sp->articles = FIXPTR(sp->articles);
  129.         if (sp->thread) {
  130.             for (ap = sp->thread; ap; ap = bump_art(ap)) {
  131.             ap->child1 = FIXPTR(ap->child1);
  132.             ap->parent = FIXPTR(ap->parent);
  133.             ap->sibling = FIXPTR(ap->sibling);
  134.             ap->subj_next = FIXPTR(ap->subj_next);
  135.             }
  136.         } else {
  137.             for (ap = sp->articles; ap; )
  138.             ap = ap->subj_next = FIXPTR(ap->subj_next);
  139.         }
  140.         }
  141.         artp = FIXPTR(artp);
  142.         curr_artp = FIXPTR(curr_artp);
  143.         recent_artp = FIXPTR(recent_artp);
  144.     }
  145.     cached_cnt = new_cnt;
  146.     }
  147.     cached_time = time((time_t*)NULL);
  148. }
  149.  
  150. void
  151. close_cache()
  152. {
  153.     SUBJECT *sp, *next;
  154.     ARTICLE *ap;
  155.     ART_NUM i;
  156.  
  157.     if (subj_hash) {
  158.     hashdestroy(subj_hash);
  159.     subj_hash = 0;
  160.     }
  161.     /* Free all the subjects. */
  162.     for (sp = first_subject; sp; sp = next) {
  163.     next = sp->next;
  164.     free(sp->str);
  165.     free((char*)sp);
  166.     }
  167.     first_subject = last_subject = Nullsubj;
  168.     subject_count = 0;            /* just to be sure */
  169.     parsed_art = 0;
  170.  
  171.     if (artptr_list) {
  172.     free((char*)artptr_list);
  173.     artptr_list = Null(ARTICLE**);
  174.     }
  175.     artptr = Null(ARTICLE**);
  176.     thread_close();
  177.  
  178.     if (cached_cnt) {
  179.     for (i = 0, ap = article_list; i < cached_cnt; i++, ap++)
  180.         clear_article(ap);
  181.     free((char*)article_list);
  182.     cached_cnt = 0;
  183.     }
  184.     cached_ng = -1;
  185. }
  186.  
  187. /* The article has all it's data in place, so add it to the list of articles
  188. ** with the same subject.
  189. */
  190. void
  191. cache_article(ap)
  192. register ARTICLE *ap;
  193. {
  194.     register ARTICLE *next, *ap2;
  195.  
  196.     if (!(next = ap->subj->articles) || ap->date < next->date)
  197.     ap->subj->articles = ap;
  198.     else {
  199.     while ((next = (ap2 = next)->subj_next) && next->date <= ap->date)
  200.         ;
  201.     ap2->subj_next = ap;
  202.     }
  203.     ap->subj_next = next;
  204.     ap->flags |= AF_CACHED;
  205.  
  206.     if (!(ap->flags & AF_READ) ^ sel_rereading) {
  207.     if (selected_only) {
  208.         if (ap->subj->flags & sel_mask) {
  209.         ap->flags |= sel_mask;
  210.         selected_count++;
  211.         }
  212.     } else if (ap->subj->flags & SF_WASSELECTED)
  213.         select_article(ap, 0);
  214.     else {
  215.         ap->subj->flags |= SF_VISIT;
  216.         if (sel_mode == SM_THREAD)
  217.         ap->subj->thread->subj->flags |= SF_VISIT;
  218.     }
  219.     }
  220. }
  221.  
  222. void
  223. check_poster(ap)
  224. register ARTICLE *ap;
  225. {
  226.     if (auto_select_postings && !(ap->flags & AF_MISSING)) {
  227.     if (ap->flags & AF_FROMTRUNCED) {
  228.         strcpy(cmd_buf,realname);
  229.         if (strEQ(ap->from,compress_name(cmd_buf,16))) {
  230.         untrim_cache = TRUE;
  231.         fetchfrom(article_num(ap),FALSE);
  232.         untrim_cache = FALSE;
  233.         }
  234.     }
  235.     if (!(ap->flags & AF_FROMTRUNCED)) {
  236.         if (instr(ap->from,phostname,FALSE)) {
  237.         if (instr(ap->from,loginName,TRUE))
  238.             select_subthread(ap,AF_AUTOSELECT);
  239.         else {
  240. #ifdef SLOW_BUT_COMPLETE_POSTER_CHECKING
  241.             char *reply_buf = fetchlines(article_num(ap),REPLY_LINE);
  242.             if (instr(reply_buf,loginName,TRUE))
  243.             select_subthread(ap,AF_AUTOSELECT);
  244.             free(reply_buf);
  245. #endif
  246.         }
  247.         }
  248.     }
  249.     }
  250. }
  251.  
  252. /* The article turned out to be a duplicate, so remove it from the cached
  253. ** list and possibly destroy the subject (should only happen if the data
  254. ** was corrupt and the duplicate id got a different subject).
  255. */
  256. void
  257. uncache_article(ap, remove_empties)
  258. register ARTICLE *ap;
  259. bool_int remove_empties;
  260. {
  261.     register ARTICLE *next, *ap2;
  262.  
  263.     if (ap->subj) {
  264.     if ((ap->flags & (AF_CACHED|AF_MISSING)) == AF_CACHED) {
  265.         if ((next = ap->subj->articles) == ap)
  266.         ap->subj->articles = ap->subj_next;
  267.         else if (next) {
  268.         while (next && (next = (ap2 = next)->subj_next) != ap)
  269.             ;
  270.         ap2->subj_next = next;
  271.         }
  272.     }
  273.     if (remove_empties && !ap->subj->articles) {
  274.         register SUBJECT *sp = ap->subj;
  275.         if (sp == first_subject)
  276.         first_subject = sp->next;
  277.         else
  278.         sp->prev->next = sp->next;
  279.         if (sp == last_subject)
  280.         last_subject = sp->prev;
  281.         else
  282.         sp->next->prev = sp->prev;
  283.         free((char*)sp);
  284.         ap->subj = Nullsubj;
  285.         subject_count--;
  286.     }
  287.     }
  288.     onemissing(ap);
  289. }
  290.  
  291. /* get the header line from an article's cache or parse the article trying */
  292.  
  293. char *
  294. fetchcache(artnum,which_line,fill_cache)
  295. ART_NUM artnum;
  296. int which_line;
  297. bool_int fill_cache;
  298. {
  299.     register char *s;
  300.     register ARTICLE *ap;
  301.     register bool cached = (htype[which_line].ht_flags & HT_CACHED);
  302.  
  303.     /* find_article() returns a Nullart if the artnum value is invalid */
  304.     if (!(ap = find_article(artnum)) || (ap->flags & AF_MISSING))
  305.     return nullstr;
  306.     if (cached && (s=get_cached_line(ap,which_line,untrim_cache)) != Nullch)
  307.     return s;
  308.     if (!fill_cache)
  309.     return Nullch;
  310.     if (!parseheader(artnum))
  311.     return nullstr;
  312.     if (cached)
  313.     return get_cached_line(ap,which_line,untrim_cache);
  314.     return Nullch;
  315. }
  316.  
  317. /* Return a pointer to a cached header line for the indicated article.
  318. ** Truncated headers (e.g. from a .thread file) are optionally ignored.
  319. */
  320. char *
  321. get_cached_line(ap, which_line, no_truncs)
  322. register ARTICLE *ap;
  323. int which_line;
  324. bool_int no_truncs;
  325. {
  326.     register char *s;
  327.  
  328.     switch (which_line) {
  329.     case SUBJ_LINE:
  330.     if (!ap->subj || (no_truncs && (ap->subj->flags & SF_SUBJTRUNCED)))
  331.         s = Nullch;
  332.     else
  333.         s = ap->subj->str + ((ap->flags & AF_HAS_RE) ? 0 : 4);
  334.     break;
  335.     case FROM_LINE:
  336.     if (no_truncs && (ap->flags & AF_FROMTRUNCED))
  337.         s = Nullch;
  338.     else
  339.         s = ap->from;
  340.     break;
  341. #ifdef DBM_XREFS
  342.     case NGS_LINE:
  343. #else
  344.     case XREF_LINE:
  345. #endif
  346.     s = ap->xrefs;
  347.     break;
  348.     case MESSID_LINE:
  349.     s = ap->msgid;
  350.     break;
  351.     default:
  352.     s = Nullch;
  353.     break;
  354.     }
  355.     return s;
  356. }
  357.  
  358. void
  359. set_subj_line(ap, subj, size)
  360. register ARTICLE *ap;
  361. register char *subj;    /* not yet allocated, so we can tweak it first */
  362. register int size;
  363. {
  364.     HASHDATUM data;
  365.     SUBJECT *sp;
  366.     char *newsubj, *subj_start;
  367.     char *t, *f;
  368.     int i;
  369.  
  370.     while (*subj && *subj != '\n' && (unsigned char)*subj <= ' ')
  371.     subj++;
  372.     if (subj != (subj_start = get_subject_start(subj))) {
  373.     if ((size -= subj_start - subj) < 0)
  374.         size = 0;
  375.     ap->flags |= AF_HAS_RE;
  376.     }
  377.     if (ap->subj && strnEQ(ap->subj->str+4, subj_start, size))
  378.     return;
  379.  
  380.     newsubj = safemalloc(size + 4 + 1);
  381.     strcpy(newsubj, "Re: ");
  382.     for (t = newsubj + 4, f = subj_start, i = size; i--; ) {
  383.     if (*f == ' ' || *f == '\t') {
  384.         while (i && (*++f == ' ' || *f == '\t'))
  385.         i--, size--;
  386.         *t++ = ' ';
  387.     } else if (*f != '\n')
  388.         *t++ = *f++;
  389.     else
  390.         f++;
  391.     }
  392.     *t = '\0';
  393.  
  394.     if (ap->subj) {
  395.     /* This only happens when we freshen truncated subjects */
  396.     hashdelete(subj_hash, ap->subj->str+4, strlen(ap->subj->str+4));
  397.     free(ap->subj->str);
  398.     ap->subj->str = newsubj;
  399.     data.dat_ptr = (char*)ap->subj;
  400.     hashstore(subj_hash, newsubj + 4, size, data);
  401.     } else {
  402.     data = hashfetch(subj_hash, newsubj + 4, size);
  403.     if (!(sp = (SUBJECT*)data.dat_ptr)) {
  404.         sp = (SUBJECT*)safemalloc(sizeof (SUBJECT));
  405.         bzero((char*)sp, sizeof (SUBJECT));
  406.         subject_count++;
  407.         if ((sp->prev = last_subject) != NULL)
  408.         sp->prev->next = sp;
  409.         else
  410.         first_subject = sp;
  411.         last_subject = sp;
  412.         sp->str = newsubj;
  413.         sp->thread_link = sp;
  414.         sp->flags = SF_THREAD;
  415.  
  416.         data.dat_ptr = (char*)sp;
  417.         hashstorelast(data);
  418.     } else
  419.         free(newsubj);
  420.     ap->subj = sp;
  421.     }
  422. }
  423.  
  424. void
  425. set_cached_line(ap, which_line, s)
  426. register ARTICLE *ap;
  427. register int which_line;
  428. register char *s;        /* already allocated, ready to save */
  429. {
  430.     /* SUBJ_LINE is handled specially above */
  431.     switch (which_line) {
  432.     case FROM_LINE:
  433.     ap->flags &= ~AF_FROMTRUNCED;
  434.     if (ap->from)
  435.         free(ap->from);
  436.     ap->from = s;
  437.     break;
  438. #ifdef DBM_XREFS
  439.     case NGS_LINE:
  440.     if (ap->xrefs && ap->xrefs != nullstr)
  441.         free(ap->xrefs);
  442.     if (!index(s, ',')) {    /* if no comma, no Xref! */
  443.         free(s);
  444.         s = nullstr;
  445.     }
  446.     ap->xrefs = s;
  447.     break;
  448. #else
  449.     case XREF_LINE:
  450.     if (ap->xrefs && ap->xrefs != nullstr)
  451.         free(ap->xrefs);
  452. # ifdef USE_NNTP
  453.     if (strEQ(s, "(none)")) {
  454.         free(s);
  455.         s = nullstr;
  456.     }
  457. # endif
  458.     ap->xrefs = s;
  459.     break;
  460. #endif
  461.     case MESSID_LINE:
  462.     if (ap->msgid)
  463.         free(ap->msgid);
  464.     ap->msgid = s;
  465.     break;
  466.     }
  467. }
  468.  
  469. int
  470. subject_cmp(key, keylen, data)
  471. char *key;
  472. int keylen;
  473. HASHDATUM data;
  474. {
  475.     /* We already know that the lengths are equal, just compare the strings */
  476.     return bcmp(key, ((SUBJECT*)data.dat_ptr)->str+4, keylen);
  477. }
  478.  
  479. /* see what we can do while they are reading */
  480.  
  481. #ifdef PENDING
  482. void
  483. look_ahead()
  484. {
  485. #ifdef ARTSEARCH
  486.     register char *h, *s;
  487.  
  488. #ifdef DEBUG
  489.     if (debug && srchahead) {
  490.     printf("(%ld)",(long)srchahead);
  491.     fflush(stdout);
  492.     }
  493. #endif
  494. #endif
  495.  
  496.     if (ThreadedGroup) {
  497.     artp = curr_artp;
  498.     inc_art(selected_only,FALSE);
  499.     if (artp)
  500.         parseheader(art);
  501.     }
  502.     else
  503. #ifdef ARTSEARCH
  504.     if (srchahead && srchahead < art) {    /* in ^N mode? */
  505.     char *pattern;
  506.  
  507.     pattern = buf+1;
  508.     strcpy(pattern,": *");
  509.     h = pattern + strlen(pattern);
  510.     interp(h,(sizeof buf) - (h-buf),"%\\s");
  511.     {            /* compensate for notesfiles */
  512.         register int i;
  513.         for (i = 24; *h && i--; h++)
  514.         if (*h == '\\')
  515.             h++;
  516.         *h = '\0';
  517.     }
  518. #ifdef DEBUG
  519.     if (debug & DEB_SEARCH_AHEAD) {
  520.         fputs("(hit CR)",stdout);
  521.         fflush(stdout);
  522.         gets(buf+128);
  523.         printf("\npattern = %s\n",pattern);
  524.     }
  525. #endif
  526.     if ((s = compile(&srchcompex,pattern,TRUE,TRUE)) != Nullch) {
  527.                     /* compile regular expression */
  528.         printf("\n%s\n",s) FLUSH;
  529.         srchahead = 0;
  530.     }
  531.     if (srchahead) {
  532.         srchahead = art;
  533.         for (;;) {
  534.         srchahead++;    /* go forward one article */
  535.         if (srchahead > lastart) { /* out of articles? */
  536. #ifdef DEBUG
  537.             if (debug)
  538.             fputs("(not found)",stdout);
  539. #endif
  540.             break;
  541.         }
  542.         if (!was_read(srchahead) &&
  543.             wanted(&srchcompex,srchahead,0)) {
  544.                     /* does the shoe fit? */
  545. #ifdef DEBUG
  546.             if (debug)
  547.             printf("(%ld)",(long)srchahead);
  548. #endif
  549.             parseheader(srchahead);
  550.             break;
  551.         }
  552.         if (input_pending())
  553.             break;
  554.         }
  555.         fflush(stdout);
  556.     }
  557.     }
  558.     else
  559. #endif /* ARTSEARCH */
  560.     {
  561.     if (art+1 <= lastart)        /* how about a pre-fetch? */
  562.         parseheader(art+1);        /* look for the next article */
  563.     }
  564. }
  565. #endif /* PENDING */
  566.  
  567. /* see what else we can do while they are reading */
  568.  
  569. void
  570. cache_until_key()
  571. {
  572. #ifdef PENDING
  573.     if (!in_ng || input_pending())
  574.     return;
  575.  
  576.     untrim_cache = TRUE;
  577.     sentinel_artp = curr_artp;
  578.  
  579.     /* Prioritize our caching based on what mode we're in */
  580.     if (mode == 't') {
  581.     if (cache_subjects())
  582.         if (cache_xrefs())
  583.         if (ThreadedGroup)
  584.             cache_all_arts();
  585.         else
  586.             cache_unread_arts();
  587.     } else {
  588.     if (!ThreadedGroup || cache_all_arts())
  589.         if (cache_subjects())
  590.         if (cache_unread_arts())
  591.             cache_xrefs();
  592.     }
  593.  
  594.     setspin(SPIN_OFF);
  595.     untrim_cache = FALSE;
  596. #endif
  597. }
  598.  
  599. #ifdef PENDING
  600. bool
  601. cache_subjects()
  602. {
  603.     register ARTICLE *ap;
  604.     register ART_NUM an;
  605.  
  606.     if (subj_to_get > lastart)
  607.     return TRUE;
  608.     setspin(SPIN_BACKGROUND);
  609.     for (an = subj_to_get, ap = article_ptr(an); an <= lastart; ap++, an++) {
  610.     if (input_pending())
  611.         break;
  612.     if (!(ap->flags & AF_READ))
  613.         fetchsubj(an,FALSE);
  614.     }
  615.     subj_to_get = an;
  616.     return subj_to_get > lastart;
  617. }
  618.  
  619. bool
  620. cache_xrefs()
  621. {
  622.     register ARTICLE *ap;
  623.     register ART_NUM an;
  624.  
  625.     if (olden_days || xref_to_get > lastart)
  626.     return TRUE;
  627.     setspin(SPIN_BACKGROUND);
  628.     for (an = xref_to_get, ap = article_ptr(an); an <= lastart; ap++, an++) {
  629.     if (input_pending())
  630.         break;
  631.     if (!(ap->flags & AF_READ))
  632.         fetchxref(an,FALSE);
  633.     }
  634.     xref_to_get = an;
  635.     return xref_to_get > lastart;
  636. }
  637.  
  638. bool
  639. cache_all_arts()
  640. {
  641.     if (!cached_all_in_range)
  642.     last_cached = first_cached-1;
  643.     if (last_cached >= lastart && first_cached <= absfirst)
  644.     return TRUE;
  645.  
  646.     /* turn it on as late as possible to avoid fseek()ing openart */
  647.     setspin(SPIN_BACKGROUND);
  648.     if (last_cached < lastart) {
  649.     if (!art_data(last_cached+1, lastart, TRUE, TRUE))
  650.         return FALSE;
  651.     cached_all_in_range = TRUE;
  652.     }
  653.     if (first_cached > absfirst) {
  654.     if (ov_opened)
  655.         ov_data(absfirst, first_cached-1, TRUE);
  656.     else
  657.         art_data(absfirst, first_cached-1, TRUE, TRUE);
  658.     /* If we got interrupted, make a quick exit */
  659.     if (first_cached > absfirst)
  660.         return FALSE;
  661.     }
  662.     /* We're all done threading the group, so if the current article is
  663.     ** still in doubt, tell them it's missing. */
  664.     if (curr_artp && !(curr_artp->flags & AF_CACHED) && !input_pending())
  665.     pushchar('\f' | 0200);
  666.     return TRUE;
  667. }
  668.  
  669. bool
  670. cache_unread_arts()
  671. {
  672.     if (last_cached >= lastart)
  673.     return TRUE;
  674.     setspin(SPIN_BACKGROUND);
  675.     return art_data(last_cached+1, lastart, TRUE, FALSE);
  676. }
  677. #endif
  678.  
  679. bool
  680. art_data(first, last, cheating, all_articles)
  681. ART_NUM first, last;
  682. bool_int cheating;
  683. bool_int all_articles;
  684. {
  685.     register ARTICLE *ap;
  686.     register ART_NUM i;
  687.     int cachemask = (ThreadedGroup ? AF_THREADED : AF_CACHED)
  688.           + (all_articles? 0 : AF_READ);
  689.  
  690.     setspin(cheating? SPIN_BACKGROUND : SPIN_FOREGROUND);
  691.     assert(first >= absfirst && last <= lastart);
  692.     for (i = first, ap = article_ptr(first); i <= last; i++, ap++) {
  693.     if (ap->flags & cachemask)
  694.         continue;
  695.     if (int_count) {
  696.         int_count = 0;
  697.         break;
  698.     }
  699.     if (cheating) {
  700.         if (input_pending())
  701.         break;
  702.         /* If the current article is no longer a '?', let them know. */
  703.         if (curr_artp != sentinel_artp) {
  704.         pushchar('\f' | 0200);
  705.         break;
  706.         }
  707.     }
  708.     /* This parses the header which will cache/thread the article */
  709.     (void) parseheader(i);
  710.     }
  711.     setspin(SPIN_POP);
  712.     if (--i > last_cached)
  713.     last_cached = i;
  714.     if (i == last) {
  715.     if (first < first_cached)
  716.         first_cached = first;
  717.     return TRUE;
  718.     }
  719.     return FALSE;
  720. }
  721.  
  722. bool
  723. cache_range(first,last)
  724. ART_NUM first;
  725. ART_NUM last;
  726. {
  727.     bool success = TRUE;
  728.     bool all_arts = (sel_rereading || thread_always);
  729.     ART_NUM count = 0;
  730.  
  731.     if (sel_rereading && !cached_all_in_range) {
  732.     first_cached = first;
  733.     last_cached = first-1;
  734.     }
  735.     if (first < first_cached)
  736.     count = first_cached-first;
  737.     if (last > last_cached)
  738.     count += last-last_cached;
  739.     if (!count)
  740.     return TRUE;
  741.  
  742.     if (first_cached > last_cached) {
  743.     if (sel_rereading) {
  744.         if (first_subject)
  745.         count -= toread[ng];
  746.     } else if (first == firstart && last == lastart && !all_arts)
  747.         count = toread[ng];
  748.     }
  749.  
  750.     printf("\n%sing %ld article%s.", ThreadedGroup? "Thread" : "Cach",
  751.        (long)count, count==1? nullstr : "s") FLUSH;
  752.  
  753.     setspin(SPIN_FOREGROUND);
  754.  
  755.     if (first < first_cached) {
  756.     if (ov_opened) {
  757.         ov_data(absfirst,first_cached-1,FALSE);
  758.         if ((success = (first_cached == absfirst)) != FALSE)
  759.         ov_close();
  760.     } else {
  761.         success = art_data(first, first_cached-1, FALSE, all_arts);
  762.         cached_all_in_range = (all_arts && success);
  763.     }
  764.     }
  765.     if (success && last_cached < last) {
  766.     success = art_data(last_cached+1, last, FALSE, all_arts);
  767.     cached_all_in_range = (all_arts && success);
  768.     }
  769.     setspin(SPIN_POP);
  770.     return success;
  771. }
  772.  
  773. void
  774. clear_article(ap)
  775. register ARTICLE *ap;
  776. {
  777.     if (ap->from)
  778.     free(ap->from);
  779.     if (ap->msgid)
  780.     free(ap->msgid);
  781.     if (ap->xrefs && ap->xrefs != nullstr)
  782.     free(ap->xrefs);
  783. }
  784.