home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / trn_12.zip / src / mt-process.c < prev    next >
C/C++ Source or Header  |  1993-12-04  |  46KB  |  1,849 lines

  1. /* $Id: mt-process.c,v 2.3 1992/12/14 00:14:03 davison Trn $
  2. */
  3.  
  4. #include "EXTERN.h"
  5. #include "common.h"
  6. #include "threads.h"
  7. #include "mthreads.h"
  8. #include "ndir.h"
  9. #ifdef SERVER
  10. #include "server.h"
  11. #endif
  12. #include "INTERN.h"
  13. #include "bits.h"
  14.  
  15. #define buff buf
  16.  
  17. char references[1024];
  18.  
  19. char subject_str[80];
  20. bool found_Re;
  21.  
  22. char author_str[20];
  23.  
  24. extern int log_verbosity, slow_down;
  25.  
  26. long num;
  27.  
  28. DOMAIN *next_domain;
  29.  
  30. void insert_article(), expire(), trim_roots(), order_roots(), trim_authors();
  31. void make_root(), use_root(), merge_roots(), set_root(), unlink_root();
  32. void link_child(), unlink_child();
  33. void free_article(), free_domain(), free_subject(), free_root(), free_author();
  34. void get_subject_str(), get_author_str();
  35. ARTICLE *get_article();
  36. SUBJECT *new_subject();
  37. AUTHOR *new_author();
  38.  
  39. #ifdef TZSET
  40. extern time_t tnow;
  41. extern long timezone;
  42. #else
  43. extern struct timeb ftnow;
  44. #endif
  45.  
  46. #ifndef SERVER
  47. static FILE *fp_article;
  48. #endif
  49.  
  50. /* Given the upper/lower bounds of the articles in the current group, add all
  51. ** the ones that we don't know about and remove all the ones that have expired.
  52. ** The current directory must be the newsgroup's spool directory.
  53. */
  54. void
  55. process_articles(first_article, last_article)
  56. ART_NUM first_article, last_article;
  57. {
  58.     register char *cp, *str;
  59.     register ARTICLE *article;
  60.     register ART_NUM i;
  61.     time_t date;
  62.     bool has_xrefs;
  63.     int len;
  64. #ifdef SERVER
  65.     bool orig_extra = extra_expire;
  66. #endif
  67. #ifdef TMPTHREAD
  68.     extern int start;
  69. #else
  70.     int start = total.last + 1;
  71. #endif
  72.     extern int errno;
  73.     extern int sys_nerr;
  74.     extern char *sys_errlist[];
  75.  
  76.     if (first_article > start) {
  77.     start = first_article;
  78.     }
  79.     added_count = last_article - start + 1;
  80.     if (added_count < 0) {
  81.     added_count = 0;
  82.     } else if (added_count > 1000) {
  83.     /* Don't overwork ourselves the first time */
  84.     added_count = 1000;
  85.     start = last_article - 1000 + 1;
  86.     }
  87.     expired_count = 0;
  88.  
  89. #ifdef TMPTHREAD
  90.     if (added_count) {
  91.     printf("\nThreading %d article%s...", added_count,
  92.         added_count == 1 ? nullstr : "s"), fflush(stdout);
  93.     }
  94. #endif
  95.  
  96.     for (i = start; i <= last_article; i++) {
  97. #ifdef TMPTHREAD
  98.     if ((i - start) % 20 == 0) {
  99.         if (i - start) {
  100.         printf("%d...", i - start), fflush(stdout);
  101.         }
  102.     }
  103. #endif
  104. #ifdef SERVER
  105.     if (slow_down) {
  106.         usleep(slow_down);
  107.     }
  108.     sprintf(buff, "HEAD %ld", (long)i);
  109.     put_server(buff);
  110.     if (get_server(buff, sizeof buff) < 0 || *buff == CHAR_FATAL) {
  111.         last_article = i - 1;
  112.         extra_expire = FALSE;
  113.         break;
  114.     }
  115.     if (*buff != CHAR_OK) {
  116.         added_count--;
  117.         continue;
  118.     }
  119. #else
  120.     /* Open article in current directory. */
  121.     sprintf(buff, "%ld", (long)i);
  122.     /* Set errno for purely paranoid reasons */
  123.     errno = 0;
  124.     if ((fp_article = fos2open(buff, "r")) == Nullfp) {
  125.         /* Missing files are ok -- they've just been expired or canceled */
  126.         if (errno != 0 && errno != ENOENT) {
  127.         if (errno < 0 || errno > sys_nerr) {
  128.             log_error("Can't open `%s': Error %d.\n", buff, errno);
  129.         } else {
  130.             log_error("Can't open `%s': %s.\n", buff,
  131.               sys_errlist[errno]);
  132.         }
  133.         }
  134.         added_count--;
  135.         continue;
  136.     }
  137. #endif
  138.  
  139.     article = Nullart;
  140.     *references = '\0';
  141.     *author_str = '\0';
  142.     *subject_str = '\0';
  143.     found_Re = 0;
  144.     date = 0;
  145.     has_xrefs = FALSE;
  146.  
  147. #ifdef SERVER
  148.     while (get_server(cp = buff, sizeof buff) == 0) {
  149.       process_line:
  150.         if (*cp == '.') {
  151.         if (cp[1]) {
  152.             log_error("Header line starts with '.'! [%ld].\n",
  153.                 (long)i);
  154.             continue;
  155.         }
  156.         break;
  157.         }
  158. #else
  159.     while ((cp = fgets(buff, sizeof buff, fp_article)) != Nullch) {
  160.       process_line:
  161.         if (*cp == '\n') {        /* check for end of header */
  162.         break;            /* break out when found */
  163.         }
  164. #endif
  165.         if ((unsigned char)*cp <= ' ') {     /* skip continuation lines */
  166.         continue;        /* (except references -- see below) */
  167.         }
  168.         if ((str = index(cp, ':')) == Nullch) {
  169. #ifdef SERVER
  170.         if (log_verbosity) {
  171.             log_error("Header line missing colon! [%ld].\n", (long)i);
  172.         }
  173.         continue;        /* skip bogus header line */
  174. #else
  175.         break;            /* end of header if no colon found */
  176. #endif
  177.         }
  178.         if ((len = str - cp) > 10) {
  179.         continue;        /* skip keywords > 10 chars */
  180.         }
  181. #ifndef SERVER
  182.         cp[strlen(cp)-1] = '\0';    /* remove newline */
  183. #endif
  184.         while (cp < str) {        /* lower-case the keyword */
  185.         if ((unsigned char)*cp <= ' ') { /* stop at any whitespace */
  186.             break;
  187.         }
  188.         if (isupper(*cp)) {
  189.             *cp = tolower(*cp);
  190.         }
  191.         cp++;
  192.         }
  193.         *cp = '\0';
  194.         cp = buff;
  195.         if (len == 4 && strEQ(cp, "date")) {
  196. #ifdef TZSET
  197.             date = get_date(str + 1, tnow, timezone);
  198. #else
  199.         date = get_date(str + 1, ftnow.time, (long) ftnow.timezone);
  200. #endif
  201.         } else
  202.         if (len == 4 && strEQ(cp, "from")) {
  203.         get_author_str(str + 1);
  204.         } else
  205.         if (len == 4 && strEQ(cp, "xref")) {
  206.         has_xrefs = TRUE;
  207.         } else
  208.         if (len == 7 && strEQ(cp, "subject")) {
  209.         get_subject_str(str + 1);
  210.         } else
  211.         if (len == 10 && strEQ(cp, "message-id")) {
  212.         if (!article) {
  213.             article = get_article(str + 1);
  214.         } else {
  215.             if (log_verbosity) {
  216.             log_error("Found multiple Message-IDs! [%ld].\n",
  217.                 (long)i);
  218.             }
  219.         }
  220.         } else
  221.         if (len == 10 && strEQ(cp, "references")) {
  222.         /* include preceding space in saved reference */
  223.         len = strlen(str + 1);
  224.         bcopy(str + 1, references, len + 1);
  225.         str = references + len;
  226.         /* check for continuation lines */
  227. #ifdef SERVER
  228.         while (get_server(cp = buff, sizeof buff) == 0) {
  229. #else
  230.         while ((cp = fgets(buff, sizeof buff, fp_article)) != Nullch) {
  231. #endif
  232.             if (*cp != ' ' && *cp != '\t') {
  233.             goto process_line;
  234.             }
  235.             while (*++cp == ' ' || *cp == '\t') {
  236.             ;
  237.             }
  238.             *--cp = ' ';
  239.             /* If the references are too long, shift them over to
  240.             ** always save the most recent ones.
  241.             */
  242.             if ((len += strlen(cp)) > 1023) {
  243.             strcpy(buff, buff + len - 1023);
  244.             str -= len - 1023;
  245.             len = 1023;
  246.             }
  247.             strcpy(str, cp);
  248.         }/* while */
  249.         break;
  250.         }/* if */
  251.     }/* while */
  252.     if (article) {
  253.         num = i;
  254.         insert_article(article, date);
  255.         if (has_xrefs) {
  256.         article->flags |= HAS_XREFS;
  257.         }
  258.     } else {
  259.         if (log_verbosity) {
  260.         log_error("Message-ID line missing! [%ld].\n", (long)i);
  261.         }
  262.     }
  263. #ifndef SERVER
  264.     fclose(fp_article);
  265. #endif
  266.     }
  267.  
  268.     if (extra_expire || first_article > total.first) {
  269.     absfirst = first_article;
  270.     lastart = last_article;
  271.     expire(first_article <= last_article ? extra_expire : FALSE);
  272.     }
  273.     trim_roots();
  274.     order_roots();
  275.     trim_authors();
  276.  
  277.     total.first = first_article;
  278.     total.last = last_article;
  279. #ifdef SERVER
  280.     extra_expire = orig_extra;
  281. #endif
  282. }
  283.  
  284. /* Search all articles for numbers less than new_first.  Traverse the list
  285. ** using the domain links so we don't have to deal with the tree structure.
  286. ** If extra is true, list all articles in the directory to setup a bitmap
  287. ** with the existing articles marked as 'read', and drop everything that
  288. ** isn't there.
  289. */
  290. void
  291. expire(extra)
  292. bool_int extra;
  293. {
  294.     register DOMAIN *domain;
  295.     register ARTICLE *article, *next_art, *hold;
  296. #ifndef TMPTHREAD
  297.     register ART_NUM art;
  298. #ifndef SERVER
  299.     register DIR *dirp;
  300. #endif
  301. #endif
  302.  
  303. #ifdef TMPTHREAD
  304.     extra = FALSE;
  305. #else
  306.     if (extra) {
  307.       MEM_SIZE ctlsize;
  308.  
  309.     /* Allocate a bitmap large enough for absfirst thru lastart. */
  310. #ifndef lint
  311.     ctlsize = (MEM_SIZE)(OFFSET(lastart)/BITSPERBYTE+20);
  312. #endif
  313.     ctlarea = safemalloc(ctlsize);
  314.     bzero(ctlarea, ctlsize);
  315.  
  316.     /* List all articles and use ctl_set() to keep track of what's there. */
  317. #ifdef SERVER
  318.     /* Try my after-market LISTGROUP command before trying XHDR. */
  319.     put_server("LISTGROUP");
  320.     if (get_server(buff, sizeof buff) != 0) {
  321.         *buff = CHAR_ERR;
  322.     }
  323.     /* If it was a command syntax error (not an unrecongnized command),
  324.     ** then they have the old LISTGROUP that requires a newsgroup name.
  325.     */
  326.     if (atoi(buff) == ERR_CMDSYN) {
  327.         extern char line[];
  328.         sprintf(buff, "LISTGROUP %s", line);
  329.         put_server(buff);
  330.         if (get_server(buff, sizeof buff) != 0) {
  331.         *buff = CHAR_ERR;
  332.         }
  333.     }
  334.     if (*buff != CHAR_OK) {
  335.         sprintf(buff, "XHDR message-id %ld-%ld",
  336.                 (long)absfirst, (long)lastart);
  337.         put_server(buff);
  338.         if (get_server(buff, sizeof buff) != 0) {
  339.         *buff = CHAR_ERR;
  340.         }
  341.     }
  342.     if (*buff == CHAR_OK) {
  343.         while (1) {
  344.         if (get_server(buff, sizeof buff) < 0) {
  345.             extra = 0;
  346.             break;
  347.         }
  348.         if (*buff == '.') {
  349.             break;
  350.         }
  351.         art = atol(buff);
  352.         if (art >= absfirst && art <= lastart) {
  353.             ctl_set(art);
  354.         }
  355.         }
  356.     } else {
  357.         extra = 0;
  358.     }
  359. #else
  360.     if ((dirp = opendir(".")) != 0) {
  361.       register struct DIRTYPE *dp;
  362.  
  363.         while ((dp = readdir(dirp)) != Null(struct DIRTYPE *)) {
  364.           register char *p;
  365.  
  366.         for (p = dp->d_name; *p; p++) {
  367.             if (!isdigit(*p)) {
  368.             goto nope;
  369.             }
  370.         }
  371.         art = atol(dp->d_name);
  372.         if (art >= absfirst && art <= lastart) {
  373.             ctl_set(art);
  374.         }
  375.       nope: ;
  376.         }
  377.         closedir(dirp);
  378.     } else {
  379.         extra = 0;
  380.     }
  381. #endif
  382.     } else {
  383.     ctlarea = Nullch;
  384.     }
  385. #endif /* TMPTHREAD */
  386.  
  387.     for (domain = &unk_domain; domain; domain = next_domain) {
  388.     next_domain = domain->link;
  389.     for (article = domain->ids; article; article = next_art) {
  390.         next_art = article->id_link;
  391.         if (!article->subject) {
  392.         continue;
  393.         }
  394.         if (article->num < absfirst
  395. #ifndef TMPTHREAD
  396.          || (extra && !ctl_read(article->num))
  397. #endif
  398.        ) {
  399.         article->subject->count--;
  400.         article->subject = 0;
  401.         article->flags &= ~HAS_XREFS;
  402.         article->author->count--;
  403.         article->author = 0;
  404.         /* Free expired article if it has no children.  Then check
  405.         ** if the parent(s) are also fake and can be freed.  We'll
  406.         ** free any empty roots later.
  407.         */
  408.         while (!article->children) {
  409.             hold = article->parent;
  410.             unlink_child(article);
  411.             free_article(article);
  412.             if (hold && !hold->subject) {
  413.             if ((article = hold) == next_art) {
  414.                 next_art = next_art->id_link;
  415.             }
  416.             } else {
  417.             break;
  418.             }
  419.         }
  420.         expired_count++;
  421.         }/* if */
  422.     }/* for */
  423.     }/* for */
  424.     next_domain = Null(DOMAIN*);
  425.  
  426. #ifndef TMPTHREAD
  427.     safefree(&ctlarea);
  428. #endif
  429. }
  430.  
  431. /* Trim the article chains down so that we don't have more than one faked
  432. ** article between the root and any real ones.
  433. */
  434. void
  435. trim_roots()
  436. {
  437.     register ROOT *root, *last_root;
  438.     register ARTICLE *article, *next;
  439.     register SUBJECT *subject, *last_subj;
  440.     register int found;
  441.  
  442. #ifndef lint
  443.     last_root = (ROOT *)&root_root;
  444. #else
  445.     last_root = Null(ROOT*);
  446. #endif
  447.     for (root = root_root; root; root = last_root->link) {
  448.     for (article = root->articles; article; article = article->siblings) {
  449.         /* If an article has no subject, it is a "fake" reference node.
  450.         ** If all of its immediate children are also fakes, delete it
  451.         ** and graduate the children to the root.  If everyone is fake,
  452.         ** the chain dies.
  453.         */
  454.         while (!article->subject) {
  455.         found = 0;
  456.         for (next = article->children; next; next = next->siblings) {
  457.             if (next->subject) {
  458.             found = 1;
  459.             break;
  460.             }
  461.         }
  462.         if (!found) {
  463.             /* Remove this faked article and move all its children
  464.             ** up to the root.
  465.             */
  466.             next = article->children;
  467.             unlink_child(article);
  468.             free_article(article);
  469.             for (article = next; article; article = next) {
  470.             next = article->siblings;
  471.             article->parent = Nullart;
  472.             link_child(article);
  473.             }
  474.             article = root->articles;    /* start this root over */
  475.         } else {
  476.             break;            /* else, on to next article */
  477.         }
  478.         }
  479.     }
  480.     /* Free all unused subject strings.  Begin by trying to find a
  481.     ** subject for the root's pointer.
  482.     */
  483.     for (subject = root->subjects; subject && !subject->count; subject = root->subjects) {
  484.         root->subjects = subject->link;
  485.         free_subject(subject);
  486.         root->subject_cnt--;
  487.     }
  488.     /* Then free up any unused intermediate subjects.
  489.     */
  490.     if ((last_subj = subject) != Null(SUBJECT*)) {
  491.         while ((subject = subject->link) != Null(SUBJECT*)) {
  492.         if (!subject->count) {
  493.             last_subj->link = subject->link;
  494.             free_subject(subject);
  495.             root->subject_cnt--;
  496.             subject = last_subj;
  497.         } else {
  498.             last_subj = subject;
  499.         }
  500.         }
  501.     }
  502.     /* Now, free all roots without articles.  Flag unexpeced errors.
  503.     */
  504.     if (!root->articles) {
  505.         if (root->subjects) {
  506.         log_error("** Empty root still had subjects remaining! **\n");
  507.         }
  508.         last_root->link = root->link;
  509.         free_root(root);
  510.     } else {
  511.         last_root = root;
  512.     }
  513.     }
  514. }
  515.  
  516. /* Descend the author list, find any author names that aren't used
  517. ** anymore and free them.
  518. */
  519. void
  520. trim_authors()
  521. {
  522.     register AUTHOR *author, *last_author;
  523.  
  524. #ifndef lint
  525.     last_author = (AUTHOR *)&author_root;
  526. #else
  527.     last_author = Null(AUTHOR*);
  528. #endif
  529.     for (author = author_root; author; author = last_author->link) {
  530.     if (!author->count) {
  531.         last_author->link = author->link;
  532.         free_author(author);
  533.     } else {
  534.         last_author = author;
  535.     }
  536.     }
  537. }
  538.  
  539. /* Reorder the roots to place the oldest ones first (age determined by
  540. ** date of oldest article).
  541. */
  542. void
  543. order_roots()
  544. {
  545.     register ROOT *root, *next, *search, *link;
  546.  
  547.     /* If we don't have at least two roots, we're done! */
  548.     if (!(root = root_root) || !(next = root->link)) {
  549.     return;                        /* RETURN */
  550.     }
  551.     /* Break the old list off after the first root, and then start
  552.     ** inserting the roots into the list by date.
  553.     */
  554.     root->link = Null(ROOT*);
  555.     while ((root = next) != Null(ROOT*)) {
  556.     next = next->link;
  557.     if ((search = root_root)->articles->date >= root->articles->date) {
  558.         root->link = root_root;
  559.         root_root = root;
  560.     } else {
  561.         register time_t radate = root->articles->date;
  562.  
  563.         while ((link = search->link) != NULL
  564.          && link->articles->date < radate) {
  565.         search = link;
  566.         }
  567.         root->link = link;
  568.         search->link = root;
  569.     }
  570.     }
  571. }
  572.  
  573. #define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y))
  574.  
  575. /* Parse the subject into 72 characters or less.  Remove any "Re[:^]"s from
  576. ** the front (noting that it's there), and any "(was: old)" stuff from
  577. ** the end.  Then, compact multiple whitespace characters into one space,
  578. ** trimming leading/trailing whitespace.  If it's still too long, unmercifully
  579. ** cut it off.  We don't bother with subject continuation lines either.
  580. */
  581. void
  582. get_subject_str(str)
  583. register char *str;
  584. {
  585.     register char *cp;
  586.     register int len;
  587.  
  588.     while (*str && (unsigned char)*str <= ' ') {
  589.     str++;
  590.     }
  591.     if (!*str) {
  592.     bcopy("<None>", subject_str, 7);
  593.     return;                        /* RETURN */
  594.     }
  595.     cp = str;
  596.     while (EQ(cp[0], 'r') && EQ(cp[1], 'e')) {    /* check for Re: */
  597.     cp += 2;
  598.     if (*cp == '^') {                /* allow Re^2: */
  599.         while (*++cp <= '9' && *cp >= '0') {
  600.         ;
  601.         }
  602.     }
  603.     if (*cp != ':') {
  604.         break;
  605.     }
  606.     while (*++cp == ' ') {
  607.         ;
  608.     }
  609.     found_Re = 1;
  610.     str = cp;
  611.     }
  612.     /* Remove "(was: oldsubject)", because we already know the old subjects.
  613.     ** Also match "(Re: oldsubject)".  Allow possible spaces after the ('s.
  614.     */
  615.     for (cp = str; (cp = index(cp+1, '(')) != Nullch;) {
  616.     while (*++cp == ' ') {
  617.         ;
  618.     }
  619.     if (EQ(cp[0], 'w') && EQ(cp[1], 'a') && EQ(cp[2], 's')
  620.      && (cp[3] == ':' || cp[3] == ' '))
  621.     {
  622.         *--cp = '\0';
  623.         break;
  624.     }
  625.     if (EQ(cp[0], 'r') && EQ(cp[1], 'e')
  626.      && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':'))) {
  627.         *--cp = '\0';
  628.         break;
  629.     }
  630.     }
  631.     /* Copy subject to a temporary string, compacting multiple spaces/tabs */
  632.     for (len = 0, cp = subject_str; len < 72 && *str; len++) {
  633.     if ((unsigned char)*str <= ' ') {
  634.         while (*++str && (unsigned char)*str <= ' ') {
  635.         ;
  636.         }
  637.         *cp++ = ' ';
  638.     } else {
  639.         *cp++ = *str++;
  640.     }
  641.     }
  642.     if (cp[-1] == ' ') {
  643.     cp--;
  644.     }
  645.     *cp = '\0';
  646. }
  647.  
  648. #ifndef OLD_AUTHOR_CODE
  649. /* Name-munging routines written by Ross Ridge.  Public Domain.
  650. ** Enhanced by Wayne Davison.
  651. */
  652.  
  653. /* If necessary, compress a net user's full name by playing games with
  654. ** initials and the middle name(s).  If we start with "Ross Douglas Ridge"
  655. ** we try "Ross D Ridge", "Ross Ridge", "R D Ridge" and finally "R Ridge"
  656. ** before simply truncating the thing.  We also turn "R. Douglas Ridge"
  657. ** into "Douglas Ridge" if it fits, otherwise it goes through the normal
  658. ** modification route.
  659. */
  660. static char *
  661. compress_name(name, max)
  662. char *name;
  663. int max;
  664. {
  665.     register char *s, *last, *mid, *d;
  666.     register int len, namelen, midlen;
  667.     int notlast;
  668.  
  669.     /* First remove white space from both ends. */
  670.     while (isspace(*name)) {
  671.     name++;
  672.     }
  673.     if ((len = strlen(name)) == 0) {
  674.     return name;
  675.     }
  676.     s = name + len - 1;
  677.     while (isspace(*s)) {
  678.     s--;
  679.     }
  680.     s[1] = '\0';
  681.     if (s - name + 1 <= max) {
  682.     return name;
  683.     }
  684.  
  685.     /* Look for characters that likely mean the end of the name
  686.     ** and the start of some hopefully uninteresting additional info.
  687.     ** Spliting at a comma is somewhat questionalble, but since
  688.     ** "Ross Ridge, The Great HTMU" comes up much more often than 
  689.     ** "Ridge, Ross" and since "R HTMU" is worse than "Ridge" we do
  690.     ** it anyways.
  691.     */
  692.     for (d = name + 1; *d; d++) {
  693.     if (*d == ',' || *d == ';' || *d == '(' || *d == '@'
  694.      || (*d == '-' && (d[1] == '-' || d[1] == ' '))) {
  695.         *d-- = '\0';
  696.         s = d;
  697.         break;
  698.     }
  699.     }
  700.  
  701.     /* Find the last name */
  702.     do {
  703.     notlast = 0;
  704.     while (isspace(*s)) {
  705.         s--;
  706.     }
  707.     s[1] = '\0';
  708.     len = s - name + 1;
  709.     if (len <= max) {
  710.         return name;
  711.     }
  712.     while (!isspace(*s)) {
  713.         if (s == name) {    /* only one name */
  714.         name[max] = '\0';
  715.         return name;
  716.         }
  717.         if (isdigit(*s)) {    /* probably a phone number */
  718.         notlast = 1;    /* so chuck it */
  719.         }
  720.         s--;
  721.     }
  722.     } while (notlast);
  723.  
  724.     last = s-- + 1;
  725.  
  726.     /* Look for a middle name */
  727.     while (isspace(*s)) {    /* get rid of any extra space */
  728.     len--;    
  729.     s--;
  730.     }
  731.     mid = name;
  732.     while (!isspace(*mid)) {
  733.     mid++;
  734.     }
  735.     namelen = mid - name + 1;
  736.     if (mid == s+1) {    /* no middle name */
  737.     mid = 0;
  738.     } else {
  739.     *mid++ = '\0';
  740.     while (isspace(*mid)) {
  741.         len--;
  742.         mid++;
  743.     }
  744.     midlen = s - mid + 2;
  745.     /* If first name is an initial and middle isn't and it all fits
  746.     ** without the first initial, drop it. */
  747.     if (len > max && mid != s && mid[1] != '.'
  748.      && (!name[1] || (name[1] == '.' && !name[2]))
  749.      && len - namelen <= max) {
  750.         len -= namelen;
  751.         name = mid;
  752.         mid = 0;
  753.     }
  754.     }
  755.     s[1] = '\0';
  756.     if (mid && len > max) {
  757.     /* Turn middle names into intials */
  758.     len -= s - mid + 2;
  759.     d = s = mid;
  760.     while (*s) {
  761.         if (isalpha(*s)) {
  762.         if (d != mid) {
  763.             *d++ = ' ';
  764.         }
  765.         *d++ = *s++;
  766.         }
  767.         while (*s && !isspace(*s)) {
  768.         s++;
  769.         }
  770.         while (isspace(*s)) {
  771.         s++;
  772.         }
  773.     }
  774.     if (d != mid) {
  775.         *d = '\0';
  776.         midlen = d - mid + 1;
  777.         len += midlen;
  778.     } else {
  779.         mid = 0;
  780.     }
  781.     }
  782.     if (len > max) {
  783.     /* If the first name fits without the middle initials, drop them */
  784.     if (mid && len - midlen <= max) {
  785.         len -= midlen;
  786.         mid = 0;
  787.     } else {
  788.         /* Turn the first name into an initial */
  789.         len -= namelen - 2;
  790.         name[1] = '\0';
  791.         namelen = 2;
  792.         if (len > max) {
  793.         /* Dump the middle initials (if present) */
  794.         if (mid) {
  795.             len -= midlen;
  796.             mid = 0;
  797.         }
  798.         if (len > max) {
  799.             /* Finally just truncate the last name */
  800.             last[max - 2] = '\0';
  801.         }
  802.         }
  803.     }
  804.     }
  805.  
  806.     /* Paste the names back together */
  807.     d = name + namelen;
  808.     if (mid) {
  809.     d[-1] = ' ';
  810.     strcpy(d, mid);
  811.     d += midlen;
  812.     }
  813.     d[-1] = ' ';
  814.     strcpy(d, last);
  815.     return name;
  816. }
  817.  
  818. /* Compress an email address, trying to keep as much of the local part of
  819. ** the addresses as possible.  The order of precence is @ ! %, but
  820. ** @ % ! may be better...
  821. */
  822. static char *
  823. compress_address(name, max)
  824. char *name;
  825. int max;
  826. {
  827.     char *s, *at, *bang, *hack, *start;
  828.     int len;
  829.  
  830.     /* Remove white space from both ends. */
  831.     while (isspace(*name)) {
  832.     name++;
  833.     }
  834.     if ((len = strlen(name)) == 0) {
  835.     return name;
  836.     }
  837.     s = name + len - 1;
  838.     while (isspace(*s)) {
  839.     s--;
  840.     }
  841.     s[1] = '\0';
  842.     if (*name == '<') {
  843.     name++;
  844.     if (*s == '>') {
  845.         *s-- = '\0';
  846.     }
  847.     }
  848.     if ((len = s - name + 1) <= max) {
  849.     return name;
  850.     }
  851.  
  852.     at = bang = hack = NULL;
  853.     for (s = name + 1; *s; s++) {
  854.     /* If there's whitespace in the middle then it's probably not
  855.     ** really an email address. */
  856.     if (isspace(*s)) {
  857.         name[max] = '\0';
  858.         return name;
  859.     }
  860.     switch (*s) {
  861.     case '@':
  862.         if (at == NULL) {
  863.         at = s;
  864.         }
  865.         break;
  866.     case '!':
  867.         if (at == NULL) {
  868.         bang = s;
  869.         hack = NULL;
  870.         }
  871.         break;
  872.     case '%':
  873.         if (at == NULL && hack == NULL) {
  874.         hack = s;
  875.         }
  876.         break;
  877.     }
  878.     }
  879.     if (at == NULL) {
  880.     at = name + len;
  881.     }
  882.  
  883.     if (hack != NULL) {
  884.     if (bang != NULL) {
  885.         if (at - bang - 1 >= max) {
  886.         start = bang + 1;
  887.         } else if (at - name >= max) {
  888.         start = at - max;
  889.         } else {
  890.         start = name;
  891.         }
  892.     } else {
  893.         start = name;
  894.     }
  895.     } else if (bang != NULL) {
  896.     if (at - name >= max) {
  897.         start = at - max;
  898.     } else {
  899.         start = name;
  900.     }
  901.     } else {
  902.     start = name;
  903.     }
  904.     if (len - (start - name) > max) {
  905.     start[max] = '\0';
  906.     }
  907.     return start;
  908. }
  909.  
  910. /* Extract the full-name part of an email address, returning NULL if not
  911. ** found.
  912. */
  913. static char *
  914. extract_name(name)
  915. char *name;
  916. {
  917.     char *s;
  918.     char *lparen, *rparen;
  919.     char *langle;
  920.  
  921.     while (isspace(*name)) {
  922.     name++;
  923.     }
  924.  
  925.     lparen = index(name, '(');
  926.     rparen = rindex(name, ')');
  927.     langle = index(name, '<');
  928.     if (!lparen && !langle) {
  929.     return NULL;
  930.     } else
  931.     if (langle && (!lparen || !rparen || lparen > langle || rparen < langle)) {
  932.     if (langle == name) {
  933.         return NULL;
  934.     }
  935.     *langle = '\0';
  936.     } else {
  937.     name = lparen;
  938.     *name++ = '\0';
  939.     while (isspace(*name)) {
  940.         name++;
  941.     }
  942.     if (name == rparen) {
  943.         return NULL;
  944.     }
  945.     if (rparen != NULL) {
  946.         *rparen = '\0';
  947.     }
  948.     }
  949.  
  950.     if (*name == '"') {
  951.     name++;
  952.     while (isspace(*name)) {
  953.         name++;
  954.     }
  955.     if ((s = rindex(name, '"')) != NULL) {
  956.         *s = '\0';
  957.     }
  958.     }
  959.     return name;
  960. }
  961.  
  962. /* Try to fit the author name in 16 bytes.  Use the comment portion if
  963. ** present.
  964. */
  965. void
  966. get_author_str(addr)
  967. char *addr;
  968. {
  969.     char *s;
  970.  
  971.     if ((s = extract_name(addr)) != NULL) {
  972.     s = compress_name(s, 16);
  973.     } else {
  974.     s = compress_address(addr, 16);
  975.     }
  976.     strcpy(author_str, s);
  977. }
  978.  
  979. #else  /* Here's the old, simple method in case someone wants it. */
  980.  
  981. /* Try to fit the author name in 16 bytes.  Use the comment portion in
  982. ** parenthesis if present.  Cut off non-commented names at the '@' or '%'.
  983. ** Then, put as many characters as we can into the 16 bytes, packing multiple
  984. ** whitespace characters into a single space.
  985. */
  986. void
  987. get_author_str(str)
  988. char *str;
  989. {
  990.     register char *cp, *cp2;
  991.  
  992.     if ((cp = index(str, '(')) != Nullch) {
  993.     str = cp+1;
  994.     if ((cp = rindex(str, ')')) != Nullch) {
  995.         *cp = '\0';
  996.     }
  997.     } else {
  998.     if ((cp = index(str, '@')) != Nullch) {
  999.         *cp = '\0';
  1000.     }
  1001.     if ((cp = index(str, '%')) != Nullch) {
  1002.         *cp = '\0';
  1003.     }
  1004.     }
  1005.     for (cp = str, cp2 = author_str; *cp && cp2-author_str < 16;) {
  1006.     /* Pack white space and turn ctrl-chars into spaces. */
  1007.     if (*cp <= ' ') {
  1008.         while (*++cp && *cp <= ' ') {
  1009.         ;
  1010.         }
  1011.         if (cp2 != author_str) {
  1012.         *cp2++ = ' ';
  1013.         }
  1014.     } else {
  1015.         *cp2++ = *cp++;
  1016.     }
  1017.     }
  1018.     *cp2 = '\0';
  1019. }
  1020. #endif
  1021.  
  1022. /* Take a message-id and see if we already know about it.  If so, return it.
  1023. ** If not, create it.  We separate the id into its id@domain parts, and
  1024. ** link all the unique ids to one copy of the domain portion.  This saves
  1025. ** a bit of space.
  1026. */
  1027. ARTICLE *
  1028. get_article(msg_id)
  1029. char *msg_id;
  1030. {
  1031.     register DOMAIN *domain;
  1032.     register ARTICLE *article;
  1033.     register char *cp, *after_at;
  1034.  
  1035.     /* Take message id, break it up into <id@domain>, and try to match it.
  1036.     */
  1037.     while (*msg_id == ' ') {
  1038.     msg_id++;
  1039.     }
  1040.     cp = msg_id + strlen(msg_id) - 1;
  1041.     if (msg_id >= cp) {
  1042.     if (log_verbosity) {
  1043.         log_error("Message-ID is empty! [%ld]\n", num);
  1044.     }
  1045.     return Nullart;
  1046.     }
  1047.     if (*msg_id++ != '<') {
  1048.     if (log_verbosity) {
  1049.         log_error("Message-ID doesn't start with '<' [%ld]\n", num);
  1050.     }
  1051.     msg_id--;
  1052.     }
  1053.     if (*cp != '>') {
  1054.     if (log_verbosity) {
  1055.         log_error("Message-ID doesn't end with '>' [%ld]\n", num);
  1056.     }
  1057.     cp++;
  1058.     }
  1059.     *cp = '\0';
  1060.     if (msg_id == cp) {
  1061.     if (log_verbosity) {
  1062.         log_error("Message-ID is null! [%ld]\n", num);
  1063.     }
  1064.     return Nullart;
  1065.     }
  1066.  
  1067.     if ((after_at = index(msg_id, '@')) == Nullch) {
  1068.     domain = &unk_domain;
  1069.     } else {
  1070.     *after_at++ = '\0';
  1071.     for (cp = after_at; *cp; cp++) {
  1072.         if (isupper(*cp)) {
  1073.         *cp = tolower(*cp);        /* lower-case domain portion */
  1074.         }
  1075.     }
  1076.     *cp = '\0';
  1077.     /* Try to find domain name in database. */
  1078.     for (domain = unk_domain.link; domain; domain = domain->link) {
  1079.         if (strEQ(domain->name, after_at)) {
  1080.         break;
  1081.         }
  1082.     }
  1083.     if (!domain) {        /* if domain doesn't exist, create it */
  1084.       register int len = cp - after_at + 1;
  1085.         domain = (DOMAIN *)safemalloc(sizeof (DOMAIN));
  1086.         total.domain++;
  1087.         domain->name = safemalloc(len);
  1088.         total.string2 += len;
  1089.         bcopy(after_at, domain->name, len);
  1090.         domain->ids = Nullart;
  1091.         domain->link = unk_domain.link;
  1092.         unk_domain.link = domain;
  1093.     }
  1094.     }
  1095.     /* Try to find id in this domain. */
  1096.     for (article = domain->ids; article; article = article->id_link) {
  1097.     if (strEQ(article->id, msg_id)) {
  1098.         break;
  1099.     }
  1100.     }
  1101.     if (!article) {        /* If it doesn't exist, create an article */
  1102.       register int len = strlen(msg_id) + 1;
  1103.     article = (ARTICLE *)safemalloc(sizeof (ARTICLE));
  1104.     bzero(article, sizeof (ARTICLE));
  1105.     total.article++;
  1106.     article->num = 0;
  1107.     article->id = safemalloc(len);
  1108.     total.string2 += len;
  1109.     bcopy(msg_id, article->id, len);
  1110.     article->domain = domain;
  1111.     article->id_link = domain->ids;
  1112.     domain->ids = article;
  1113.     }
  1114.     return article;
  1115. }
  1116.  
  1117. /* Take all the data we've accumulated about the article and shove it into
  1118. ** the article tree at the best place we can possibly imagine.
  1119. */
  1120. void
  1121. insert_article(article, date)
  1122. ARTICLE *article;
  1123. time_t date;
  1124. {
  1125.     register ARTICLE *node, *last;
  1126.     register char *cp, *end;
  1127.     int len;
  1128.  
  1129.     if (article->subject) {
  1130.     if (log_verbosity) {
  1131.         log_error("We've already seen article #%ld (%s@%s)\n",
  1132.         num, article->id, article->domain->name);
  1133.     }
  1134.     return;                        /* RETURN */
  1135.     }
  1136.     article->date = date;
  1137.     article->num = num;
  1138.     article->flags = 0;
  1139.  
  1140.     if (!*references && found_Re) {
  1141.     if (log_verbosity > 1) {
  1142.         log_error("Missing reference line!  [%ld]\n", num);
  1143.     }
  1144.     }
  1145.     /* If the article has a non-zero root, it is already in a thread somewhere.
  1146.     ** Unlink it to try to put it in the best possible spot.
  1147.     */
  1148.     if (article->root) {
  1149.     /* Check for a real or shared-fake parent.  Articles that have never
  1150.     ** existed have a num of 0.  Expired articles that remain as references
  1151.     ** have a valid num.  (Valid date too, but no subject.)
  1152.     */
  1153.     for (node = article->parent;
  1154.          node && !node->num && node->child_cnt == 1;
  1155.          node = node->parent)
  1156.     {
  1157.         ;
  1158.     }
  1159.     unlink_child(article);
  1160.     if (node) {            /* do we have decent parents? */
  1161.         /* Yes: assume that our references are ok, and just reorder us
  1162.         ** with our siblings by date.
  1163.         */
  1164.         link_child(article);
  1165.         use_root(article, article->root);
  1166.         /* Freshen the date in any faked parent articles. */
  1167.         for (node = article->parent;
  1168.          node && !node->num && date < node->date;
  1169.          node = node->parent)
  1170.         {
  1171.         node->date = date;
  1172.         unlink_child(node);
  1173.         link_child(node);
  1174.         }
  1175.         return;                    /* RETURN */
  1176.     }
  1177.     /* We'll assume that this article has as good or better references
  1178.     ** than the child that faked us initially.  Free the fake reference-
  1179.     ** chain and process our references as usual.
  1180.     */
  1181.     for (node = article->parent; node; node = last) {
  1182.          unlink_child(node);
  1183.         last = node->parent;
  1184.         free_article(node);
  1185.     }
  1186.     article->parent = Nullart;        /* neaten up */
  1187.     article->siblings = Nullart;
  1188.     }
  1189.   check_references:
  1190.     if (!*references) {    /* If no references but "Re:" in subject, */
  1191.     if (found_Re) {    /* search for a reference in any cited text */
  1192. #ifndef SERVER
  1193.         for (len = 4; len && fgets(buff, sizeof buff, fp_article); len--) {
  1194.         if ((cp = index(buff, '<')) && (end = index(cp, ' '))) {
  1195.             if (end[-1] == ',') {
  1196.             end--;
  1197.             }
  1198.             *end = '\0';
  1199.             if ((end = index(cp, '>')) == Nullch) {
  1200.             end = cp + strlen(cp) - 1;
  1201.             }
  1202.             if (valid_message_id(cp, end)) {
  1203.             strcpy(references+1, cp);
  1204.             *references = ' ';
  1205.             if (log_verbosity > 2) {
  1206.                 log_error("Found cited-text reference: '%s' [%ld]\n",
  1207.                 references+1, num);
  1208.             }
  1209.             break;
  1210.             }
  1211.         }
  1212.         }
  1213. #endif
  1214.     } else {
  1215.         article->flags |= ROOT_ARTICLE;
  1216.     }
  1217.     }
  1218.     /* If we have references, process them from the right end one at a time
  1219.     ** until we either run into somebody, or we run out of references.
  1220.     */
  1221.     if (*references) {
  1222.     last = article;
  1223.     node = Nullart;
  1224.     end = references + strlen(references) - 1;
  1225.     while ((cp = rindex(references, '<')) != Nullch) {
  1226.         while (end >= cp && ((unsigned char)*end <= ' ' || *end == ',')) {
  1227.         end--;
  1228.         }
  1229.         end[1] = '\0';
  1230.         /* Quit parsing references if this one is garbage. */
  1231.         if (!valid_message_id(cp, end)) {
  1232.         if (log_verbosity) {
  1233.             log_error("Bad ref '%s' [%ld]\n", cp, num);
  1234.         }
  1235.         break;
  1236.         }
  1237.         /* Dump all domains that end in '.', such as "..." & "1@DEL." */
  1238.         if (end[-1] == '.') {
  1239.         break;
  1240.         }
  1241.         node = get_article(cp);
  1242.         *cp = '\0';
  1243.  
  1244.         /* Check for duplicates on the reference line.  Brand-new data has
  1245.         ** no date.  Data we just allocated earlier on this line has a
  1246.         ** date but no root.  Special-case the article itself, since it
  1247.         ** MIGHT have a root.
  1248.         */
  1249.         if ((node->date && !node->root) || node == article) {
  1250.         if (log_verbosity) {
  1251.             log_error("Reference line contains duplicates [%ld]\n",
  1252.             num);
  1253.         }
  1254.         if ((node = last) == article) {
  1255.             node = Nullart;
  1256.         }
  1257.         continue;
  1258.         }
  1259.         last->parent = node;
  1260.         link_child(last);
  1261.         if (node->root) {
  1262.         break;
  1263.         }
  1264.         node->date = date;
  1265.         last = node;
  1266.         end = cp-1;
  1267.     }
  1268.     if (!node) {
  1269.         *references = '\0';
  1270.         goto check_references;
  1271.     }
  1272.     /* Check if we ran into anybody that was already linked.  If so, we
  1273.     ** just use their root.
  1274.     */
  1275.     if (node->root) {
  1276.         /* See if this article spans the gap between what we thought
  1277.         ** were two different roots.
  1278.         */
  1279.         if (article->root && article->root != node->root) {
  1280.         merge_roots(node->root, article->root);
  1281.         /* Set the roots of any children we brought with us. */
  1282.         set_root(article, node->root);
  1283.         }
  1284.         use_root(article, node->root);
  1285.     } else {
  1286.         /* We didn't find anybody we knew, so either create a new root or
  1287.         ** use the article's root if it was previously faked.
  1288.         */
  1289.         if (!article->root) {
  1290.         make_root(node);
  1291.         use_root(article, node->root);
  1292.         } else {
  1293.         node->root = article->root;
  1294.         link_child(node);
  1295.         use_root(article, article->root);
  1296.         }
  1297.     }
  1298.     /* Set the roots of the faked articles we created as references. */
  1299.     for (node = article->parent; node && !node->root; node = node->parent) {
  1300.         node->root = article->root;
  1301.     }
  1302.     /* Make sure we didn't circularly link to a child article(!), by
  1303.     ** ensuring that we run into the root before we run into ourself.
  1304.     */
  1305.     while (node && node->parent != article) {
  1306.         node = node->parent;
  1307.     }
  1308.     if (node) {
  1309.         /* Ugh.  Someone's tweaked reference line with an incorrect
  1310.         ** article-order arrived first, and one of our children is
  1311.         ** really one of our ancestors. Cut off the bogus child branch
  1312.         ** right where we are and link it to the root.
  1313.         */
  1314.         if (log_verbosity) {
  1315.         log_error("Found ancestral child -- fixing.\n");
  1316.         }
  1317.         unlink_child(node);
  1318.         node->parent = Nullart;
  1319.         link_child(node);
  1320.     }
  1321.     } else {
  1322.     /* The article has no references.  Either turn it into a new root, or
  1323.     ** re-attach fleshed-out (previously faked) article to its old root.
  1324.     */
  1325.     if (!article->root) {
  1326.         make_root(article);
  1327.     } else {
  1328.         link_child(article);
  1329.         use_root(article, article->root);
  1330.     }
  1331.     }
  1332. }
  1333.  
  1334. /* Check if the string we've found looks like a valid message-id reference.
  1335. */
  1336. int
  1337. valid_message_id(start, end)
  1338. register char *start, *end;
  1339. {
  1340.     char *mid;
  1341.  
  1342.     if (start == end) {
  1343.     return 0;
  1344.     }
  1345.  
  1346.     if (*end != '>') {
  1347.     /* Compensate for space cadets who include the header in their
  1348.     ** subsitution of all '>'s into another citation character.
  1349.     */
  1350.     if (*end == '<' || *end == '-' || *end == '!' || *end == '%'
  1351.      || *end == ')' || *end == '|' || *end == ':' || *end == '}'
  1352.      || *end == '*' || *end == '+' || *end == '#' || *end == ']'
  1353.      || *end == '@' || *end == '$') {
  1354.         if (log_verbosity) {
  1355.         log_error("Reference ended in '%c' [%ld]\n", *end, num);
  1356.         }
  1357.         *end = '>';
  1358.     }
  1359.     } else if (end[-1] == '>') {
  1360.     if (log_verbosity) {
  1361.         log_error("Reference ended in '>>' [%ld]\n", num);
  1362.     }
  1363.     *(end--) = '\0';
  1364.     }
  1365.     /* Id must be "<...@...>" */
  1366.     if (*start != '<' || *end != '>' || (mid = index(start, '@')) == Nullch
  1367.      || mid == start+1 || mid+1 == end) {
  1368.     return 0;                    /* RETURN */
  1369.     }
  1370.     return 1;
  1371. }
  1372.  
  1373. /* Remove an article from its parent/siblings.  Leave parent pointer intact.
  1374. */
  1375. void
  1376. unlink_child(child)
  1377. register ARTICLE *child;
  1378. {
  1379.     register ARTICLE *last;
  1380.  
  1381.     if (!(last = child->parent)) {
  1382.     child->root->thread_cnt--;
  1383.     if ((last = child->root->articles) == child) {
  1384.         child->root->articles = child->siblings;
  1385.     } else {
  1386.         goto sibling_search;
  1387.     }
  1388.     } else {
  1389.     last->child_cnt--;
  1390.     if (last->children == child) {
  1391.         last->children = child->siblings;
  1392.     } else {
  1393.         last = last->children;
  1394.       sibling_search:
  1395.         while (last->siblings != child) {
  1396.         last = last->siblings;
  1397.         }
  1398.         last->siblings = child->siblings;
  1399.     }
  1400.     }
  1401. }
  1402.  
  1403. /* Link an article to its parent article.  If its parent pointer is zero,
  1404. ** link it to its root.  Sorts siblings by date.
  1405. */
  1406. void
  1407. link_child(child)
  1408. register ARTICLE *child;
  1409. {
  1410.     register ARTICLE *node;
  1411.     register ROOT *root;
  1412.  
  1413.     if (!(node = child->parent)) {
  1414.     root = child->root;
  1415.     root->thread_cnt++;
  1416.     node = root->articles;
  1417.     if (!node || child->date < node->date) {
  1418.         child->siblings = node;
  1419.         root->articles = child;
  1420.     } else {
  1421.         goto sibling_search;
  1422.     }
  1423.     } else {
  1424.     node->child_cnt++;
  1425.     node = node->children;
  1426.     if (!node || child->date < node->date) {
  1427.         child->siblings = node;
  1428.         child->parent->children = child;
  1429.     } else {
  1430.       sibling_search:
  1431.         for (; node->siblings; node = node->siblings) {
  1432.         if (node->siblings->date > child->date) {
  1433.             break;
  1434.         }
  1435.         }
  1436.         child->siblings = node->siblings;
  1437.         node->siblings = child;
  1438.     }
  1439.     }
  1440. }
  1441.  
  1442. /* Create a new root for the specified article.  If the current subject_str
  1443. ** matches any pre-existing root's subjects, we'll instead add it on as a
  1444. ** parallel thread.
  1445. */
  1446. void
  1447. make_root(article)
  1448. register ARTICLE *article;
  1449. {
  1450.     register ROOT *new, *node;
  1451.     register SUBJECT *subject;
  1452.  
  1453. #ifndef NO_SUBJECT_MATCHING
  1454.     /* First, check the other root's subjects for a match. */
  1455.     for (node = root_root; node; node = node->link) {
  1456.     for (subject = node->subjects; subject; subject = subject->link) {
  1457.         if (subject_equal(subject->str, subject_str)) {
  1458.         use_root(article, node);        /* use it instead */
  1459.         link_child(article);
  1460.         return;                    /* RETURN */
  1461.         }
  1462.     }
  1463.     }
  1464. #endif
  1465.  
  1466.     /* Create a new root. */
  1467.     new = (ROOT *)safemalloc(sizeof (ROOT));
  1468.     total.root++;
  1469.     new->articles = article;
  1470.     new->root_num = article->num;
  1471.     new->thread_cnt = 1;
  1472.     if (article->num) {
  1473.     article->author = new_author();
  1474.     new->subject_cnt = 1;
  1475.     new->subjects = article->subject = new_subject();
  1476.     } else {
  1477.     new->subject_cnt = 0;
  1478.     new->subjects = Null(SUBJECT*);
  1479.     }
  1480.     article->root = new;
  1481.     new->link = root_root;
  1482.     root_root = new;
  1483. }
  1484.  
  1485. /* Add this article's subject onto the indicated root's list.  Point the
  1486. ** article at the root.
  1487. */
  1488. void
  1489. use_root(article, root)
  1490. ARTICLE *article;
  1491. ROOT *root;
  1492. {
  1493.     register SUBJECT *subject;
  1494.     register ROOT *root2;
  1495.     SUBJECT *hold, *child_subj = Null(SUBJECT*), *sib_subj = Null(SUBJECT*);
  1496.     ARTICLE *node;
  1497.  
  1498.     article->root = root;
  1499.  
  1500.     /* If it's a fake, there's no subject to add. */
  1501.     if (!article->num) {
  1502.     return;                        /* RETURN */
  1503.     }
  1504.  
  1505.     /* If we haven't picked a unique message number to represent this root,
  1506.     ** use the first non-zero number we encounter.  Which one doesn't matter.
  1507.     */
  1508.     if (!root->root_num) {
  1509.     root->root_num = article->num;
  1510.     }
  1511.     article->author = new_author();
  1512.  
  1513.     /* Check if the new subject matches any of the other subjects in this root.
  1514.     ** If so, we just update the count.  If not, check all the other roots for
  1515.     ** a match.  If found, the new subject is common between the two roots, so
  1516.     ** we merge the two roots together.
  1517.     */
  1518.     root2 = root;
  1519. #ifndef NO_SUBJECT_MATCHING
  1520.     do {
  1521. #endif
  1522.     for (subject = root2->subjects; subject; subject = subject->link) {
  1523.         if (subject_equal(subject->str, subject_str)) {
  1524.         article->subject = subject;
  1525.         subject->count++;
  1526. #ifndef NO_SUBJECT_MATCHING
  1527.         if (root2 != root) {
  1528.             merge_roots(root, root2);
  1529.         }
  1530. #endif
  1531.         return;                    /* RETURN */
  1532.         }
  1533.     }
  1534. #ifndef NO_SUBJECT_MATCHING
  1535.     if ((root2 = root2->link) == Null(ROOT*)) {
  1536.         root2 = root_root;
  1537.     }
  1538.     } while (root2 != root);
  1539. #endif
  1540.  
  1541.     article->subject = hold = new_subject();
  1542.     root->subject_cnt++;
  1543.  
  1544.     /* Find the subject of any pre-existing children or siblings.  We want
  1545.     ** to insert the new subject before one of these to keep the numbering
  1546.     ** intuitive in the newsreader.  Never insert prior to our parent's
  1547.     ** subject, however.
  1548.     */
  1549.     for (node = article->children; node; node = node->children) {
  1550.     if (node->subject) {
  1551.         child_subj = node->subject;
  1552.         break;
  1553.     }
  1554.     }
  1555.     for (node = article->siblings; node; node = node->siblings) {
  1556.     if (node->subject) {
  1557.         sib_subj = node->subject;
  1558.         break;
  1559.     }
  1560.     }
  1561.     if (article->parent) {
  1562.     if (article->parent->subject == child_subj) {
  1563.         child_subj = Null(SUBJECT*);
  1564.     }
  1565.     if (article->parent->subject == sib_subj) {
  1566.         sib_subj = Null(SUBJECT*);
  1567.     }
  1568.     }
  1569.     if (!(subject = root->subjects)
  1570.      || subject == child_subj || subject == sib_subj) {
  1571.     hold->link = root->subjects;
  1572.     root->subjects = hold;
  1573.     } else {
  1574.     while (subject->link
  1575.      && subject->link != child_subj && subject->link != sib_subj) {
  1576.         subject = subject->link;
  1577.     }
  1578.     hold->link = subject->link;
  1579.     subject->link = hold;
  1580.     }
  1581. }
  1582.  
  1583. /* Check subjects in a case-insignificant, punctuation-ignoring manner.
  1584. */
  1585. int
  1586. subject_equal(str1, str2)
  1587. register char *str1, *str2;
  1588. {
  1589.     register char ch1, ch2;
  1590.  
  1591.     while ((ch1 = *str1++)) {
  1592.     if (ch1 == ' ' || ispunct(ch1)) {
  1593.         while (*str1 && (*str1 == ' ' || ispunct(*str1))) {
  1594.         str1++;
  1595.         }
  1596.         ch1 = ' ';
  1597.     } else if (isupper(ch1)) {
  1598.         ch1 = tolower(ch1);
  1599.     }
  1600.     if (!(ch2 = *str2++)) {
  1601.         return 0;
  1602.     }
  1603.     if (ch2 == ' ' || ispunct(ch2)) {
  1604.         while (*str2 && (*str2 == ' ' || ispunct(*str2))) {
  1605.         str2++;
  1606.         }
  1607.         ch2 = ' ';
  1608.     } else if (isupper(ch2)) {
  1609.         ch2 = tolower(ch2);
  1610.     }
  1611.     if (ch1 != ch2) {
  1612.         return 0;
  1613.     }
  1614.     }
  1615.     if (*str2) {
  1616.     return 0;
  1617.     }
  1618.     return 1;
  1619. }
  1620.  
  1621. /* Create a new subject structure. */
  1622. SUBJECT *
  1623. new_subject()
  1624. {
  1625.     register int len = strlen(subject_str) + 1;
  1626.     register SUBJECT *subject;
  1627.  
  1628.     subject = (SUBJECT *)safemalloc(sizeof (SUBJECT));
  1629.     total.subject++;
  1630.     subject->count = 1;
  1631.     subject->link = Null(SUBJECT*);
  1632.     subject->str = safemalloc(len);
  1633.     total.string1 += len;
  1634.     bcopy(subject_str, subject->str, len);
  1635.  
  1636.     return subject;
  1637. }
  1638.  
  1639. /* Create a new author structure. */
  1640. AUTHOR *
  1641. new_author()
  1642. {
  1643.     register len = strlen(author_str) + 1;
  1644.     register AUTHOR *author, *last_author;
  1645.  
  1646.     last_author = Null(AUTHOR*);
  1647.     for (author = author_root; author; author = author->link) {
  1648. #ifndef DONT_COMPARE_AUTHORS    /* might like to define this to save time */
  1649.     if (strEQ(author->name, author_str)) {
  1650.         author->count++;
  1651.         return author;                /* RETURN */
  1652.     }
  1653. #endif
  1654.     last_author = author;
  1655.     }
  1656.  
  1657.     author = (AUTHOR *)safemalloc(sizeof (AUTHOR));
  1658.     total.author++;
  1659.     author->count = 1;
  1660.     author->link = Null(AUTHOR*);
  1661.     author->name = safemalloc(len);
  1662.     total.string1 += len;
  1663.     bcopy(author_str, author->name, len);
  1664.  
  1665.     if (last_author) {
  1666.     last_author->link = author;
  1667.     } else {
  1668.     author_root = author;
  1669.     }
  1670.     return author;
  1671. }
  1672.  
  1673. /* Insert all of root2 into root1, setting the proper root values and
  1674. ** updating subject counts.
  1675. */
  1676. void
  1677. merge_roots(root1, root2)
  1678. ROOT *root1, *root2;
  1679. {
  1680.     register ARTICLE *node, *next;
  1681.     register SUBJECT *subject;
  1682.  
  1683.     /* Remember whoever's root num is lower.  This could screw up a
  1684.     ** newsreader's kill-thread code if someone already saw the roots as
  1685.     ** being separate, but it must be done.  The newsreader code will have
  1686.     ** to handle this as best as it can.
  1687.     */
  1688.     if (root1->root_num > root2->root_num) {
  1689.     root1->root_num = root2->root_num;
  1690.     }
  1691.  
  1692.     for (node = root2->articles; node; node = next) {
  1693.     /* For each article attached to root2: detach it, set the branch's
  1694.     ** root pointer to root1, and then attach it to root1.
  1695.     */
  1696.     next = node->siblings;
  1697.     unlink_child(node);
  1698.     node->siblings = Nullart;
  1699.     set_root(node, root1);        /* sets children too */
  1700.     /* Link_child() depends on node->parent being null and node->root
  1701.     ** being set.
  1702.     */
  1703.     link_child(node);
  1704.     }
  1705.     root1->subject_cnt += root2->subject_cnt;
  1706.     if (!(subject = root1->subjects)) {
  1707.     root1->subjects = root2->subjects;
  1708.     } else {
  1709.     while (subject->link) {
  1710.         subject = subject->link;
  1711.     }
  1712.     subject->link = root2->subjects;
  1713.     }
  1714.     unlink_root(root2);
  1715.     free_root(root2);
  1716. }
  1717.  
  1718. /* When merging roots, we need to reset all the root pointers.
  1719. */
  1720. void
  1721. set_root(node, root)
  1722. ARTICLE *node;
  1723. ROOT *root;
  1724. {
  1725.     do {
  1726.     node->root = root;
  1727.     if (node->children) {
  1728.         set_root(node->children, root);
  1729.     }
  1730.     } while (node = node->siblings);
  1731. }
  1732.  
  1733. /* Unlink a root from its neighbors. */
  1734. void
  1735. unlink_root(root)
  1736. register ROOT *root;
  1737. {
  1738.     register ROOT *node;
  1739.  
  1740.     if ((node = root_root) == root) {
  1741.     root_root = root->link;
  1742.     } else {
  1743.     while (node->link != root) {
  1744.         node = node->link;
  1745.     }
  1746.     node->link = root->link;
  1747.     }
  1748. }
  1749.  
  1750. /* Free an article and its message-id string.  All other resources must
  1751. ** already be free, and it must not be attached to any threads.
  1752. */
  1753. void
  1754. free_article(this)
  1755. ARTICLE *this;
  1756. {
  1757.     register ARTICLE *art;
  1758.  
  1759.     if ((art = this->domain->ids) == this) {
  1760.     if (!(this->domain->ids = this->id_link)) {
  1761.         free_domain(this->domain);
  1762.     }
  1763.     } else {
  1764.     while (this != art->id_link) {
  1765.         art = art->id_link;
  1766.     }
  1767.     art->id_link = this->id_link;
  1768.     }
  1769.     total.string2 -= strlen(this->id) + 1;
  1770.     free(this->id);
  1771.     free(this);
  1772.     total.article--;
  1773. }
  1774.  
  1775. /* Free the domain only when its last unique id has been freed. */
  1776. void
  1777. free_domain(this)
  1778. DOMAIN *this;
  1779. {
  1780.     register DOMAIN *domain;
  1781.  
  1782.     if (this == (domain = &unk_domain)) {
  1783.     return;
  1784.     }
  1785.     if (this == next_domain) {    /* help expire routine skip freed domains */
  1786.     next_domain = next_domain->link;
  1787.     }
  1788.     while (this != domain->link) {
  1789.     domain = domain->link;
  1790.     }
  1791.     domain->link = this->link;
  1792.     total.string2 -= strlen(this->name) + 1;
  1793.     free(this->name);
  1794.     free(this);
  1795.     total.domain--;
  1796. }
  1797.  
  1798. /* Free the subject structure and its string. */
  1799. void
  1800. free_subject(this)
  1801. SUBJECT *this;
  1802. {
  1803.     total.string1 -= strlen(this->str) + 1;
  1804.     free(this->str);
  1805.     free(this);
  1806.     total.subject--;
  1807. }
  1808.  
  1809. /* Free a root.  It must already be unlinked. */
  1810. void
  1811. free_root(this)
  1812. ROOT *this;
  1813. {
  1814.     free(this);
  1815.     total.root--;
  1816. }
  1817.  
  1818. /* Free the author structure when it's not needed any more. */
  1819. void
  1820. free_author(this)
  1821. AUTHOR *this;
  1822. {
  1823.     total.string1 -= strlen(this->name) + 1;
  1824.     free(this->name);
  1825.     free(this);
  1826.     total.author--;
  1827. }
  1828.  
  1829. #if defined(SERVER) && !defined(USLEEP)
  1830. usleep(usec)
  1831. long usec;
  1832. {
  1833. # ifndef USELECT
  1834.     if (usec /= 1000000) {
  1835.     sleep((int)usec);
  1836.     }
  1837. # else
  1838.     struct timeval t;
  1839.  
  1840.     if (usec <= 0) {
  1841.     return;
  1842.     }
  1843.     t.tv_usec = usec % 1000000;
  1844.     t.tv_sec  = usec / 1000000;
  1845.     (void) select(1, 0, 0, 0, &t);
  1846. # endif
  1847. }
  1848. #endif
  1849.