home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / snews-20.zip / SNEWS.C < prev    next >
C/C++ Source or Header  |  1992-08-09  |  39KB  |  1,466 lines

  1. /*
  2.     SNEWS 2.0
  3.  
  4.     snews - a simple threaded news reader
  5.  
  6.  
  7.     Copyright (C) 1991  John McCombs, Christchurch, NEW ZEALAND
  8.                         john@ahuriri.gen.nz
  9.                         PO Box 2708, Christchurch, NEW ZEALAND
  10.  
  11.     This program is free software; you can redistribute it and/or modify
  12.     it under the terms of the GNU General Public License, version 1, as
  13.     published by the Free Software Foundation.
  14.  
  15.     This program is distributed in the hope that it will be useful,
  16.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  17.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18.     GNU General Public License for more details.
  19.  
  20.     See the file COPYING, which contains a copy of the GNU General
  21.     Public License.
  22.  
  23.  */
  24.  
  25. #include "defs.h"
  26. #include "snews.h"
  27.  
  28.  
  29. INFO my_stuff;
  30. char iobuf[IOBUFSIZE];
  31. char search_text[256];
  32.  
  33.  
  34. /*------------------------------- main --------------------------------*/
  35. void main(void)
  36. {
  37.     ACTIVE *gp, *head;
  38.     int    done;
  39.  
  40.     ignore_signals();
  41.     
  42.     printf("loading config...\n");
  43.     if (load_stuff()) {
  44.  
  45.         printf("loading active...\n");
  46.         head = load_active_file();
  47.         printf("loading read list...\n");
  48.         load_read_list();
  49.         printf("loading history...\n");
  50.         load_history_list();
  51.  
  52.         scrinit();
  53.         clrscr();
  54.  
  55.         done = FALSE;
  56.         gp = NULL;
  57.  
  58.         while (!done) {
  59.             if ((gp = select_group(head, gp)) != NULL) {
  60.                 done |= read_group(gp);
  61.             } else {
  62.                 done = TRUE;
  63.             }
  64.         }
  65.  
  66.         screxit();
  67.         clrscr();
  68.  
  69.         free_hist_list();
  70.         save_read_list();
  71.         close_active_file();
  72.  
  73.     } else {
  74.         fprintf(stderr, "Couldn't find necessary item in the .rc files\n");
  75.     }
  76. }
  77.  
  78.  
  79.  
  80.  
  81. /*-------------------------- find which group to read -----------------------*/
  82. ACTIVE *select_group(ACTIVE *head, ACTIVE *current)
  83. {
  84.     /*
  85.      *  Present the list of groups, and allow him to move up and down with
  86.      *  the arrow and PgUp and PgDn keys.
  87.      */
  88.  
  89.     ACTIVE *top;        /* newsgroup at the top of the page */
  90.     ACTIVE *this;       /* current newsgroup                */
  91.     ACTIVE *tmp_ng;
  92.     int    exit_code;   /* why we are exiting the loop      */
  93.  
  94.     int    ch, i, j, articles, unread;
  95.  
  96.     this = (current == NULL) ? head : current;
  97.  
  98.     top = head;
  99.     exit_code = 0;
  100.  
  101.     show_groups(&top, this, TRUE, head);
  102.  
  103.     while (exit_code == 0) {
  104.  
  105.         ch = getch();
  106.         switch (ch) {
  107.  
  108.             case 0      :
  109.             case 0xE0   :
  110.             
  111.                 ch = getch();
  112.                 switch (ch) {
  113.  
  114.                     case F1     :
  115.                         show_help(HELP_GROUP);
  116.                         show_groups(&top, this, TRUE, head);
  117.                         break;
  118.                         
  119.                     case UP_ARR :
  120.                         if (this->last != NULL) this = this->last;
  121.                         break;
  122.  
  123.                     case DN_ARR :
  124.                         if (this->next != NULL) this = this->next;
  125.                         break;
  126.  
  127.                     case PGUP   :
  128.                         for (i = 0; i < PAGE_LENGTH; i++) {
  129.                             if (this->last == NULL) break;
  130.                             this = this->last;
  131.                         }
  132.                         break;
  133.  
  134.                     case PGDN   :
  135.                         for (i = 0; i < PAGE_LENGTH; i++) {
  136.                             if (this->next == NULL) break;
  137.                             this = this->next;
  138.                         }
  139.                         break;
  140.  
  141.                     case HOME   :
  142.                         top = this = head;
  143.                         show_groups(&top, this, TRUE, head);
  144.                         break;
  145.  
  146.                     case END    :
  147.                         this = head;
  148.                         while (this->next != NULL)
  149.                             this = this->next;
  150.                         break;
  151.                 }
  152.                 break;
  153.  
  154.             case 'p'    :
  155.             case 'P'    :
  156.                 post(NULL, this->group);
  157.                 show_groups(&top, this, TRUE, head);
  158.                 break;
  159.  
  160.             case 'h'    :
  161.             case 'H'    :
  162.                 show_help(HELP_GROUP);
  163.                 show_groups(&top, this, TRUE, head);
  164.                 break;
  165.  
  166.             case 'c'    :
  167.             case 'C'    :
  168.                 mark_group_as_read(this);
  169.                 show_groups(&top, this, TRUE, head);
  170.                 break;
  171.  
  172.             case TAB    :
  173.                 tmp_ng = this->next;
  174.                 unread = 0;
  175.                 while (tmp_ng != NULL) {
  176.                     articles = (int) (tmp_ng->hi_num - tmp_ng->lo_num);
  177.                     for (j = 0; j < articles; j++) {
  178.                         if ( *((tmp_ng->read_list)+j) == FALSE)
  179.                             unread++;
  180.                     }
  181.                     if (unread > 0) break;
  182.                     tmp_ng = tmp_ng->next;
  183.                 }
  184.                 if (unread > 0) {
  185.                     this = tmp_ng;
  186.                 } else {
  187.                     message("-- No more articles to read --");
  188.                 }
  189.                 break;
  190.  
  191.             case ENTER  :
  192.                 exit_code = EX_DONE;
  193.                 break;
  194.  
  195.             case ESCAPE :
  196.                 exit_code = EX_QUIT;
  197.                 break;
  198.         };
  199.         if (exit_code == 0)
  200.             show_groups(&top, this, FALSE, head);
  201.     }
  202.  
  203.     if (exit_code == EX_DONE)
  204.         return(this);
  205.     else
  206.         return(NULL);
  207.  
  208. }
  209.  
  210.  
  211.  
  212. /*---------------------------- help screen ----------------------------------*/
  213.  
  214. void help(char *text)
  215. {
  216.     printf("%*s", _columns / 2 - 40, "");
  217.  
  218.     while (*text) {
  219.         if (*text == '\n')
  220.             clreol(), _line++;
  221.         putch(*text++);
  222.     }
  223. }
  224.  
  225. void show_help(int h)
  226. {
  227.     char *type[] = {"Group Help",  "Thread Help",  "Article Help"};
  228.     char *msg = COPYRIGHT;
  229.     int x;
  230.  
  231.     gotoxy(1,1);
  232.     textbackground(LIGHTGRAY);  textcolor(BLACK);
  233.     printf("%-18s %*s", type[h], _columns - 19, VERSION);
  234.     gotoxy(1,2);
  235.     x = _columns / 2 - strlen(msg) / 2;
  236.     printf("%*s%s", x, "", msg);
  237.     clreol();
  238.     textbackground(BLACK);  textcolor(LIGHTGRAY);
  239.     putch('\n');
  240.  
  241.     help("\n");
  242.     help("         PageUp - move display up one page\n");
  243.     help("         PageDn - move display down one page\n");
  244.     help("           Home - move to top of list/article\n");
  245.     help("            End - move to bottom of list/article\n");
  246.     help("             Up - move up one line\n");
  247.     help("           Down - move down one line\n");
  248.  
  249.     switch (h) {
  250.         case HELP_GROUP   :
  251.             help("\n");
  252.             help("            Tab - go to next newgroup with unread articles\n");
  253.             help("          Enter - enter selected newsgroup\n\n");
  254.             help("              c - mark all articles in selected group as read\n");
  255.             help("              p - post article to selected group\n");
  256.             break;
  257.  
  258.         case HELP_THREAD  :
  259.             help("\n");
  260.             help("            Tab - go to next thread with unread articles or\n");
  261.             help("                  enter this thread if it is already selected\n");
  262.             help("                  and move to the first unread article in the thread\n");
  263.             help("          Enter - read the selected thread from the beginning\n");
  264.             help("      Backspace - go to the last article in the selected thread\n\n");
  265.             help("              c - mark all articles in selected thread as read\n");
  266.             help("              s - save selected thread to disk\n");
  267.             help("              p - post article to this group\n\n");
  268.         help("              + - search for text in subjects\n");
  269.         help("              / - search for text in article bodies\n");
  270.             break;
  271.  
  272.         case HELP_ARTICLES:
  273.             help("           Left - scroll right\n");
  274.             help("          Right - scroll left\n\n");
  275.             help("            Tab - go to next unread article\n");
  276.             help("          Enter - go to next article in thread\n");
  277.             help("      Backspace - go to previous article in thread\n\n");
  278.             help("              s - save this article to disk\n");
  279.             help("              p - post new article to current group\n");
  280.             help("              f - post follow-up to this article\n");
  281.             help("              r - reply to sender of this article via e-mail\n");
  282.             help("              m - mail this article to someone\n");
  283.         help("            + / - search for text in following articles\n");
  284.         help("            - ? - search for text in preceding articles\n");
  285.             break;
  286.     };
  287.  
  288.     clreos();
  289.     message("-- Press any key to continue --");
  290.     getch();
  291.  
  292. }
  293.  
  294.  
  295.  
  296.  
  297. /*-------------------- show the list of active groups -----------------------*/
  298. void show_groups(ACTIVE **top, ACTIVE *this, int force, ACTIVE *head)
  299. {
  300.     /*
  301.      *  This routine takes 'top', a pointer to the first line on the screen
  302.      *  and 'this' a pointer to where we want to be, and updates the screen.
  303.      *  A maker to this is maintained, and the screen is repainted, where
  304.      *  necessary
  305.      */
  306.  
  307.     static last_y;
  308.     static last_index;
  309.     int    i, ur;
  310.     ACTIVE *that;
  311.     char buf[32];
  312.  
  313.     /*
  314.      *  If 'this' is above the 'top' or it is more than a screen length below,
  315.      *  or'this and 'top' are both zero, ie first time repaint the screen
  316.      */
  317.     /* if ((((*top)->index == 0) && (this->index == 0)) || force ||
  318.         ((*top)->index > this->index) ||
  319.         (this->index - (*top)->index) > PAGE_LENGTH-1) { */
  320.     if ( force ||
  321.         ((*top)->index > this->index) ||
  322.         (this->index - (*top)->index) > PAGE_LENGTH-1) {
  323.  
  324.         gotoxy(1,1);
  325.         textbackground(LIGHTGRAY);  textcolor(BLACK);
  326.         sprintf(buf, "Group %d of %d", last_index + 1, head->groups);
  327.         printf("Select Group %*s", _columns - 13, buf);
  328.     gotoxy(1,2);
  329.         clreol();
  330.         textbackground(BLACK);  textcolor(LIGHTGRAY);
  331.  
  332.         /* now adjust the top */
  333.         *top = this;
  334.         for (i = 0; i < PAGE_LENGTH/2; i++) {
  335.             if ((*top)->last == NULL) break;
  336.             *top = (*top)->last;
  337.         }
  338.  
  339.         that = *top;
  340.         for (i = 0; i < PAGE_LENGTH+1; i++) {
  341.             gotoxy(1, i+PAGE_HEADER);
  342.             if (that == NULL) break;
  343.             ur = count_unread_in_group(that);
  344.             if (ur > 0)
  345.                 printf("    %4d %4ld %s", ur,
  346.                        that->hi_num - that->lo_num, that->group);
  347.             else
  348.                 printf("         %4ld %s",
  349.                        that->hi_num - that->lo_num, that->group);
  350.             clreol();
  351.             that = that->next;
  352.         }
  353.  
  354.         clreos();
  355.         last_y = this->index - (*top)->index;
  356.         gotoxy(2, last_y + PAGE_HEADER);
  357.         puts("->");
  358.         last_index = this->index;
  359.  
  360.     } else {
  361.  
  362.         gotoxy(2, last_y + PAGE_HEADER);
  363.         puts("  ");
  364.  
  365.         last_y += (this->index - last_index);
  366.         gotoxy(2, last_y + PAGE_HEADER);
  367.         puts("->");
  368.         last_index = this->index;
  369.  
  370.     }
  371.  
  372.     gotoxy(_columns - 16,1);
  373.     textbackground(LIGHTGRAY);  textcolor(BLACK);
  374.     sprintf(buf, "Group %d of %d", last_index + 1, head->groups);
  375.     printf("%17s", buf);
  376.     textbackground(BLACK);  textcolor(LIGHTGRAY);
  377.  
  378.     message("ESC=quit   TAB=next unread group   ENTER=read group   F1=help");
  379. }
  380.  
  381.  
  382. /*--------------------------- process message -------------------------------*/
  383. int read_group(ACTIVE *gp)
  384. {
  385.     /*
  386.      *  We now have newsgroup.  Access the directory and try to read
  387.      *  the newsgroup articles, extracting the headers.
  388.      */
  389.  
  390.     ARTICLE *start;
  391.  
  392.     if (gp->lo_num < gp->hi_num) {
  393.         start = get_headers(gp);
  394.         select_thread(gp, start);
  395.         free_header(start);
  396.     }
  397.  
  398.     return(0);
  399. }
  400.  
  401.  
  402. /*-------------------- show the list of active groups -----------------------*/
  403. void show_threads(ACTIVE *gp, ARTICLE **top, ARTICLE *this, int force,
  404.                   ARTICLE *head)
  405. {
  406.     /*
  407.      *  This routine takes 'top', a pointer to the first line on the screen
  408.      *  and 'this' a pointer to where we want to be, and updates the screen.
  409.      *  A maker to this is maintained, and the screen is repainted, where
  410.      *  necessary
  411.      */
  412.  
  413.     static last_y;
  414.     static last_index;
  415.     int    i;
  416.     ARTICLE *that;
  417.     int    ur;
  418.     char buf[32];
  419.  
  420.     /*
  421.      *  If 'this' is above the 'top' or it is more than a screen length below,
  422.      *  or'this and 'top' are both zero, ie first time repaint the screen
  423.      */
  424.     /* if ((((*top)->index == 0) && (this->index == 0)) || force ||
  425.         ((*top)->index > this->index) ||
  426.         (this->index - (*top)->index) > PAGE_LENGTH-1) { */
  427.     if ( force ||
  428.         ((*top)->index > this->index) ||
  429.         (this->index - (*top)->index) > PAGE_LENGTH-1) {
  430.  
  431.         for (that = head, ur = 0; that; that = that->next)
  432.             ur += count_unread_in_thread(gp, that);
  433.  
  434.         gotoxy(1,1);
  435.         textbackground(LIGHTGRAY);  textcolor(BLACK);
  436.         sprintf(buf, "Thread %d of %d", last_index + 1, gp->threads);
  437.         printf("Select Thread %*s", _columns - 14, buf);
  438.     gotoxy(1,2);
  439.         sprintf(buf, "%ld Articles, %d unread",
  440.                 gp->hi_num - gp->lo_num, ur);
  441.         printf("Group: %-*.*s %26s", _columns - 34, _columns - 34,
  442.                gp->group, buf);
  443.         textbackground(BLACK);  textcolor(LIGHTGRAY);
  444.  
  445.         /* now adjust the top */
  446.         *top = this;
  447.         for (i = 0; i < PAGE_LENGTH/2; i++) {
  448.             if ((*top)->last == NULL) break;
  449.             *top = (*top)->last;
  450.         }
  451.  
  452.         that = *top;
  453.         for (i = 0; i < PAGE_LENGTH+1; i++) {
  454.             gotoxy(1, i+PAGE_HEADER);
  455.             if (that == NULL) break;
  456.             ur = count_unread_in_thread(gp, that);
  457.             if (ur > 0)
  458.                 printf("    %4d %4d  %s", ur,
  459.                        that->num_articles, that->header);
  460.             else
  461.                 printf("         %4d  %s",
  462.                        that->num_articles, that->header);
  463.             clreol();
  464.             that = that->next;
  465.         }
  466.  
  467.         clreos();
  468.  
  469.         last_y = this->index - (*top)->index;
  470.         gotoxy(2, last_y + PAGE_HEADER);
  471.         puts("->");
  472.         last_index = this->index;
  473.  
  474.     } else {
  475.  
  476.         gotoxy(2, last_y + PAGE_HEADER);
  477.         puts("  ");
  478.  
  479.         last_y += (this->index - last_index);
  480.         gotoxy(2, last_y + PAGE_HEADER);
  481.         puts("->");
  482.         last_index = this->index;
  483.  
  484.         gotoxy(_columns - 17,1);
  485.         textbackground(LIGHTGRAY);  textcolor(BLACK);
  486.         sprintf(buf, "Thread %d of %d", last_index + 1, gp->threads);
  487.         printf("%18s", buf);
  488.         textbackground(BLACK);  textcolor(LIGHTGRAY);
  489.  
  490.     }
  491.  
  492.     message("ESC=select group   TAB=next unread   ENTER=next article   F1=help");
  493. }
  494.  
  495.  
  496.  
  497. /*--------------- search through threads ------------------------------------*/
  498. ARTICLE *search_thread(ACTIVE *gp, ARTICLE *head, int search_body)
  499. {
  500.     int offset = 0, found = FALSE, irq = FALSE;
  501.     ARTICLE *this;
  502.     ART_ID *art;
  503.     TEXT *tx;
  504.     LINE *text;
  505.     char *fn;
  506.     char prompt[128], pattern[128];
  507.  
  508.     if (head == NULL || head->next == NULL)
  509.       return head;
  510.  
  511.     sprintf(prompt, "Search %s for? [%s] ",
  512.         search_body ? "articles" : "subjects", search_text);
  513.     lmessage(prompt);
  514.     if (gets(pattern) == NULL)
  515.       return head;
  516.  
  517.     if (strlen(pattern) > 0)
  518.       strcpy(search_text, pattern);
  519.     else
  520.       strcpy(pattern, search_text);
  521.  
  522.     strlwr(pattern);
  523.  
  524.     if (search_body) {
  525.  
  526.     message("*** searching - please wait (press ESC to abort) ***");
  527.     tflush();
  528.  
  529.     for (this = head->next; this != NULL; this = this->next) {
  530.  
  531.         /* for each article */
  532.         for (art = this->art_num; art != NULL; art = art->next_art) {
  533.         
  534.         fn = make_news_group_name(gp->group);
  535.         tx = load_article(fn, art->art_off);
  536.  
  537.         for (text = tx->start; !found && text != NULL; 
  538.              text = text->next) {
  539.             strlwr(text->data);
  540.             found = (strstr(text->data, pattern) != NULL);
  541.         }
  542.     
  543.         free_article(tx);
  544.  
  545.         if (kbhit())
  546.           irq = (getch() == 27);
  547.  
  548.         if (found || irq)
  549.           break;
  550.         }
  551.  
  552.         if (found || irq) 
  553.           break;
  554.     }
  555.  
  556.     } else {
  557.  
  558.     for (this = head->next; this != NULL; this = this->next) {
  559.  
  560.         strcpy(prompt, this->header);
  561.         strlwr(prompt);
  562.         found = (strstr(prompt, pattern) != NULL);
  563.  
  564.         if (found)
  565.           break;
  566.     }
  567.  
  568.     }
  569.  
  570.     if (!found && !irq) {
  571.     sprintf(prompt, "*** '%s' not found - press any key  ***", pattern);
  572.     message(prompt);
  573.     getch();
  574.     }
  575.  
  576.     return found ? this : head;
  577. }
  578.  
  579.  
  580.  
  581. /*-------------------------- find which thread to read ----------------------*/
  582. void select_thread(ACTIVE *gp, ARTICLE *head)
  583. {
  584.     /*
  585.      *  Present the list of threads, and allow him to move up and down with
  586.      *  the arrow and PgUp and PgDn keys.
  587.      */
  588.  
  589.     ARTICLE *top;        /* thread at the top of the page    */
  590.     ARTICLE *this;       /* current thread                   */
  591.     ARTICLE *th;
  592.     ART_ID  *art;
  593.     int    exit_code;    /* why we are exiting the loop      */
  594.  
  595.     int    ch, i, idx, hit, a_ct;
  596.  
  597.     this = head;
  598.     top = head;
  599.     exit_code = 0;
  600.  
  601.     show_threads(gp, &top, this, TRUE, head);
  602.  
  603.     while (exit_code == 0) {
  604.  
  605.         ch = getch();
  606.         switch (ch) {
  607.  
  608.             case 0      :
  609.             case 0xE0   :
  610.                 ch = getch();
  611.  
  612.                 switch (ch) {
  613.  
  614.                     case F1     :
  615.                     show_help(HELP_THREAD);
  616.                     show_threads(gp, &top, this, TRUE, head);
  617.                         break;
  618.                         
  619.                     case UP_ARR :
  620.                         if (this->last != NULL) this = this->last;
  621.                         break;
  622.  
  623.                     case DN_ARR :
  624.                         if (this->next != NULL) this = this->next;
  625.                         break;
  626.  
  627.                     case PGUP   :
  628.                         for (i = 0; i < PAGE_LENGTH; i++) {
  629.                             if (this->last == NULL) break;
  630.                             this = this->last;
  631.                         }
  632.                         break;
  633.  
  634.                     case PGDN   :
  635.                         for (i = 0; i < PAGE_LENGTH; i++) {
  636.                             if (this->next == NULL) break;
  637.                             this = this->next;
  638.                         }
  639.                         break;
  640.  
  641.                     case HOME   :
  642.                         top = this = head;
  643.                         show_threads(gp, &top, this, TRUE, head);
  644.                         break;
  645.                     case END    :
  646.                         this = head;
  647.                         while (this->next != NULL)
  648.                             this = this->next;
  649.                         break;
  650.                 }
  651.                 break;
  652.  
  653.             case 's'    :
  654.             case 'S'    :
  655.                 save_thread_to_disk(gp, this);
  656.                 break;
  657.  
  658.             case 'p'    :
  659.             case 'P'    :
  660.                 post(NULL, gp->group);
  661.                 show_threads(gp, &top, this, TRUE, head);
  662.                 break;
  663.  
  664.             case 'h'    :
  665.             case 'H'    :
  666.                 show_help(HELP_THREAD);
  667.                 show_threads(gp, &top, this, TRUE, head);
  668.                 break;
  669.  
  670.             case TAB    :
  671.                 /*
  672.                  *  Go to the next unread article.  Work through each
  673.                  *  thread, looking at each article to see if it's been
  674.                  *  read
  675.                  */
  676.  
  677.                 /* for each thread */
  678.                 th = this;
  679.                 hit = FALSE;
  680.                 while (th != NULL) {
  681.  
  682.                     art = th->art_num;
  683.                     a_ct = 0;
  684.  
  685.                     /* for each article */
  686.                     while (art != NULL) {
  687.                         idx = (int)(art->id - gp->lo_num - 1);
  688.                         a_ct++;
  689.                         if ( *((gp->read_list)+idx) == FALSE) {
  690.                             hit = TRUE;
  691.                             break;
  692.                         }
  693.                         art = art->next_art;
  694.                     }
  695.                     if (hit) break;
  696.                     th = th->next;
  697.                 }
  698.  
  699.                 if (hit) {
  700.                     if (this == th )
  701.                         read_thread(gp, this, art, a_ct);
  702.                     this = th;
  703.                     show_threads(gp, &top, this, TRUE, head);
  704.                 }
  705.                 break;
  706.  
  707.             case 'c'    :
  708.             case 'C'    :
  709.                 mark_thread_as_read(gp, this);
  710.                 show_threads(gp, &top, this, TRUE, head);
  711.                 break;
  712.  
  713.             case ENTER  :
  714.                 read_thread(gp, this, this->art_num, 1);
  715.                 show_threads(gp, &top, this, TRUE, head);
  716.                 break;
  717.  
  718.             case BACKSP :
  719.             art = this->art_num;
  720.         a_ct = 1;
  721.         while (art->next_art != NULL) {
  722.             a_ct++;
  723.             art = art->next_art;
  724.         }
  725.                 read_thread(gp, this, art, a_ct);
  726.                 show_threads(gp, &top, this, TRUE, head);
  727.                 break;
  728.  
  729.         case '+'    :
  730.         case '/'    :
  731.             this = search_thread(gp, this, ch == '/');
  732.                 show_threads(gp, &top, this, TRUE, head);
  733.                 break;
  734.  
  735.             case ESCAPE :
  736.                 exit_code = EX_QUIT;
  737.                 break;
  738.         };
  739.         if (exit_code == 0)
  740.             show_threads(gp, &top, this, FALSE, head);
  741.     }
  742.  
  743.  
  744. }
  745.  
  746.  
  747.  
  748.  
  749. /*------------------------ save a thread ------------------------------*/
  750.  
  751. int cmp(TEXT **art1, TEXT **art2)
  752. {
  753.     char *subj1 = (*art1) -> subject, *subj2 = (*art2) -> subject;
  754.     while (isspace(*subj1)) subj1++;
  755.     while (isspace(*subj2)) subj2++;
  756.     return strcmp(subj1, subj2);
  757. }
  758.  
  759. void save_thread_to_disk(ACTIVE *gp, ARTICLE *this)
  760. {
  761.     ART_ID *id;
  762.     char   *fn;
  763.     TEXT   *tx;
  764.     LINE   *ln;
  765.     char   fnx[256];
  766.     int    ch, art, idx;
  767.     TEXT   *text[MAXART];
  768.     FILE   *tmp = NULL;
  769.     time_t now;
  770.     struct tm *tmnow;
  771.     char timestr[64];
  772.     
  773.     lmessage("Enter filename? ");
  774.     gets(fnx);
  775.  
  776.     if (access(fnx, 0) == 0) {
  777.  
  778.         message("File exists - append(y/n)? ");
  779.         while (((ch = getch()) != 'y') && (ch != 'n'));
  780.         if (ch == 'y') {
  781.             if ((tmp = fopen(fnx, "at")) == NULL) {
  782.                 message("*** cannot open file for appending - "
  783.                         "please any key ***");
  784.                 getch();
  785.             }
  786.         }
  787.  
  788.     } else {
  789.  
  790.         if ((tmp = fopen(fnx, "wt")) == NULL) {
  791.             message("*** cannot open file for output - press any key ***");
  792.             getch();
  793.         }
  794.     
  795.     setvbuf(tmp, iobuf, _IOFBF, IOBUFSIZE);
  796.     }
  797.  
  798.     if (tmp != NULL) {
  799.  
  800.         fn = make_news_group_name(gp->group);
  801.  
  802.         id = this->art_num;
  803.     idx = 0;
  804.     
  805.         while (id != NULL && idx < MAXART) {
  806.         text[idx++] = load_article(fn, id->art_off);
  807.             id = id->next_art;
  808.         }
  809.  
  810.     qsort(text, idx, sizeof(TEXT *), cmp);
  811.     
  812.         time(&now);
  813.     tmnow = localtime(&now);
  814.     strftime(timestr, sizeof(timestr), "%a %b %d %H:%M:%S %z %Y", tmnow);
  815.  
  816.     for ( art = 0; art < idx; art++ ) {
  817.         
  818.             tx = text[art];
  819.  
  820.         ln = tx->top;
  821.         while (ln != NULL && strncmp(ln->data, "Path: ", 6)) {
  822.              ln = ln->next;
  823.         }
  824.         strcpy(fn, ln->data + 6);
  825.         for ( ch = strlen(fn); fn[ch - 1] == '\n' || fn[ch - 1] == '\r'; ch--)
  826.             fn[ch - 1] = 0;
  827.         fprintf(tmp, "From %s %s\n", fn, timestr);
  828.  
  829.             ln = tx->top;
  830.             while (ln != NULL) {
  831.                 fputs(ln->data, tmp);
  832.                 ln = ln->next;
  833.             }
  834.  
  835.             fputs("\n\n", tmp);
  836.             free_article(tx);
  837.     }
  838.     
  839.         fclose(tmp);
  840.     }
  841. }
  842.  
  843.  
  844.  
  845. /*------------------------ search through articles --------------------*/
  846. int search_message(char *fn, ART_ID *current, int forward)
  847. {
  848.     int offset = 0, found = FALSE;
  849.     TEXT *tx;
  850.     LINE *text;
  851.     char prompt[128], pattern[128];
  852.  
  853.     sprintf(prompt, "Search %s for? [%s] ",
  854.         forward ? "forward" : "backward", search_text);
  855.     lmessage(prompt);
  856.     if (gets(pattern) == NULL)
  857.       return 0;
  858.  
  859.     if (strlen(pattern) > 0)
  860.       strcpy(search_text, pattern);
  861.     else
  862.       strcpy(pattern, search_text);
  863.  
  864.     strlwr(pattern);
  865.  
  866.     for (;;) {
  867.  
  868.     current = forward ? current->next_art : current->last_art;
  869.     if (current == NULL)
  870.       break;
  871.  
  872.     offset++;
  873.         tx = load_article(fn, current->art_off);
  874.  
  875.     for (text = tx->start; !found && text != NULL; text = text->next) {
  876.         strlwr(text->data);
  877.         found = (strstr(text->data, pattern) != NULL);
  878.     }
  879.     
  880.         free_article(tx);
  881.  
  882.     if (found)
  883.       break;
  884.     }
  885.  
  886.     if (!found) {
  887.     sprintf(prompt, "*** '%s' not found - press any key  ***", pattern);
  888.     message(prompt);
  889.     getch();
  890.     }
  891.  
  892.     return found ? offset : 0;
  893. }
  894.  
  895.  
  896. /*------------------------ read a thread ------------------------------*/
  897. int read_thread(ACTIVE *gp, ARTICLE *this, ART_ID *first, int a_ct)
  898. {
  899.     ART_ID *id;
  900.     char   *fn;
  901.     int    idx, res;
  902.     TEXT   *tx;
  903.     char   author[128], msg_id[128];
  904.     CROSS_POSTS *h, *h0;
  905.     ACTIVE *gx;
  906.  
  907.  
  908.     fn = make_news_group_name(gp->group);
  909.  
  910.     id = first;
  911.  
  912.     while (id != NULL) {
  913.  
  914.         tx = load_article(fn, id->art_off);
  915.  
  916.         res = read_article(gp, tx, a_ct, this->num_articles);
  917.  
  918.         /* mark this article as read */
  919.         idx = (int) ((id->id) - gp->lo_num - 1);
  920.         (gp->read_list)[idx] = TRUE;
  921.  
  922.         /* mark the crossposts */
  923.         get_his_stuff(tx, author, msg_id);
  924.         if ((h0 = look_up_history(msg_id, gp->group)) != NULL) {
  925.  
  926.             h = h0;
  927.             while (h != NULL) {
  928.                 gx = find_news_group(h->group);
  929.                 idx = (int) ((h->art_num) - gx->lo_num - 1);
  930.                 if ( 0 <= idx && idx < gx->hi_num )
  931.                   (gx->read_list)[idx] = TRUE;
  932.                 h = h->next;
  933.             }
  934.  
  935.             free_cross_post_list(h0);
  936.         }
  937.  
  938.         free_article(tx);
  939.  
  940.         if (res == EX_QUIT) break;
  941.  
  942.         if (res == EX_NEXT_UNREAD) {
  943.  
  944.             while (id != NULL) {
  945.                 idx = (int)(id->id - gp->lo_num - 1);
  946.                 if ( *((gp->read_list)+idx) == FALSE) {
  947.                     break;
  948.                 }
  949.                 a_ct++;
  950.                 id = id->next_art;
  951.             }
  952.  
  953.         } else if (res == EX_SEARCH_FORW) {
  954.         res = search_message(fn, id, 1);
  955.         while (res--) {
  956.         a_ct++;
  957.                 id = id->next_art;
  958.         }
  959.         } else if (res == EX_SEARCH_BACKW) {
  960.         res = search_message(fn, id, 0);
  961.         while (res--) {
  962.         a_ct--;
  963.         id = id->last_art;
  964.         }
  965.         } else if (res == EX_PREVIOUS) {
  966.             a_ct--;
  967.             id = id->last_art;
  968.         } else {
  969.             a_ct++;
  970.             id = id->next_art;
  971.         }
  972.     }
  973.  
  974.     return(res);
  975. }
  976.  
  977.  
  978.  
  979.  
  980. /*------------------------- read the headers --------------------------*/
  981.  
  982. int strip_off_part(char *str)
  983. {
  984.   char *ptr = str + strlen(str);
  985.  
  986.   /* strip off (case-insensitive) things like:
  987.      - "Part01/10"
  988.      - "Part 01/10"
  989.      - "Part 01 of 10"
  990.      - "[1/10]"
  991.      - "(1 of 10)"
  992.      - "1 of 10"
  993.      - "Patch02a/04"
  994.      - "Patch20"
  995.    */
  996.  
  997.   while ( ptr > str && ptr[-1] == ' ' )
  998.     ptr--;
  999.  
  1000.   if ( ptr > str && (ptr[-1] == ')' || ptr[-1] == ']') )
  1001.     ptr--;
  1002.  
  1003.   while ( ptr > str && isdigit(ptr[-1]) )
  1004.     ptr--;
  1005.  
  1006.   if ( !isdigit(*ptr) )
  1007.     return 0;
  1008.  
  1009.   if ( ptr > str && ptr[-1] == '/' )
  1010.     ptr--;
  1011.   else if ( ptr > str + 3 && strnicmp(ptr - 4, " of ", 4) == 0 )
  1012.     ptr -= 4;
  1013.   else if ( ptr > str + 4 && strnicmp(ptr - 5, "Patch", 5) == 0 )
  1014.   {
  1015.     ptr -= 5;
  1016.     goto label;
  1017.   }
  1018.   else if ( ptr > str + 5 && strnicmp(ptr - 6, "Patch ", 6) == 0 )
  1019.   {
  1020.     ptr -= 6;
  1021.     goto label;
  1022.   }
  1023.   else
  1024.     return 0;
  1025.  
  1026.   if ( ptr > str && 'a' <= ptr[-1] && ptr[-1] <= 'z' )
  1027.     ptr--;
  1028.  
  1029.   while ( ptr > str && isdigit(ptr[-1]) )
  1030.     ptr--;
  1031.  
  1032.   if ( !isdigit(*ptr) )
  1033.     return 0;
  1034.  
  1035.   if ( ptr > str && (ptr[-1] == '(' || ptr[-1] == '[') )
  1036.     ptr--;
  1037.  
  1038.   while ( ptr > str && ptr[-1] == ' ' )
  1039.     ptr--;
  1040.  
  1041.   if ( ptr > str + 3 && strnicmp(ptr - 4, "Part", 4) == 0 )
  1042.     ptr -= 4;
  1043.  
  1044. label:    
  1045.   while ( ptr > str && ptr[-1] == ' ' )
  1046.     ptr--;
  1047.  
  1048.   if ( ptr > str && ptr[-1] == ',' )
  1049.     ptr--;
  1050.   else if ( ptr > str && ptr[-1] == ':' )
  1051.     ptr--;
  1052.  
  1053.   *ptr = 0;
  1054.   return 1;
  1055. }
  1056.  
  1057. char *skip_vi(char *str)
  1058. {
  1059.   char *ptr = str;
  1060.  
  1061.   /* skip things like "v02i0027: " */
  1062.  
  1063.   while ( isspace(*ptr) )
  1064.     ptr++;
  1065.  
  1066.   if ( *ptr++ != 'v' )
  1067.     return str;
  1068.  
  1069.   if ( !isdigit(*ptr) )
  1070.     return str;
  1071.  
  1072.   while ( isdigit(*ptr) )
  1073.     ptr++;
  1074.  
  1075.   if ( *ptr++ != 'i' )
  1076.     return str;
  1077.  
  1078.   if ( !isdigit(*ptr) )
  1079.     return str;
  1080.  
  1081.   while ( isdigit(*ptr) )
  1082.     ptr++;
  1083.  
  1084.   if ( *ptr++ != ':' )
  1085.     return str;
  1086.  
  1087.   if ( *ptr++ != ' ' )
  1088.     return str;
  1089.  
  1090.   while ( isspace(*ptr) )
  1091.     ptr++;
  1092.  
  1093.   return ptr;
  1094. }
  1095.  
  1096. int smartcmp(char *str1, char *str2)
  1097. {
  1098.   char s1[256], s2[256];
  1099.  
  1100.   strcpy(s1, str1);
  1101.   strcpy(s2, str2);
  1102.  
  1103.   if ( strip_off_part(s1) && strip_off_part(s2) )
  1104.   {
  1105.     str1 = skip_vi(s1);
  1106.     str2 = skip_vi(s2);
  1107.   }
  1108.  
  1109.   return stricmp(str1, str2);
  1110. }
  1111.  
  1112. ARTICLE *get_headers(ACTIVE *gp)
  1113. {
  1114.     /*
  1115.      *  Read the files and get the headers
  1116.      */
  1117.  
  1118.     char *fn;
  1119.     char buf[MAXLINE], fnx[256], *buf_p;
  1120.     long g, n_read;
  1121.     FILE *tmp_file;
  1122.  
  1123.     ARTICLE *start, *that, *tmp;
  1124.     ARTICLE **ptr;
  1125.     ART_ID  *art_this, *new;
  1126.     int     ct_art, cmp;
  1127.  
  1128.     n_read = 0;
  1129.     ct_art = 0;
  1130.     start = NULL;
  1131.  
  1132.     fn = make_news_group_name(gp->group);
  1133.     sprintf(fnx, "%s.IDX", fn);
  1134.  
  1135.     if ((tmp_file = flockopen(fnx, "rb")) != NULL) {
  1136.  
  1137.         setvbuf(tmp_file, iobuf, _IOFBF, IOBUFSIZE);
  1138.  
  1139.         for (g = gp->lo_num+1; g <= gp->hi_num; g++) {
  1140.  
  1141.             if ((n_read++ % 100) == 0) {
  1142.                 gotoxy(1,PAGE_SIZE);
  1143.                 textbackground(LIGHTGRAY);  textcolor(BLACK);
  1144.                 printf("%d articles processed", n_read-1);
  1145.                 clreol();
  1146.                 textbackground(BLACK);  textcolor(LIGHTGRAY);
  1147.                 tflush();
  1148.             }
  1149.  
  1150.             /*
  1151.              *  Read the index
  1152.              *  Search the linked list for the subject
  1153.              *    - allocate a new subject if necessary
  1154.              *    - add to the existing list
  1155.              */
  1156.  
  1157.             if (fgets(buf, sizeof(buf), tmp_file) == NULL) {
  1158.                 gotoxy(1,PAGE_SIZE);
  1159.                 fprintf(stderr, "\nsnews: index file is corrupt\n");
  1160.                 exit(1);
  1161.             }
  1162.  
  1163.             /* check all is in sync */
  1164.             if (g != atol(buf+9)) {
  1165.                 gotoxy(1,PAGE_SIZE);
  1166.                 fprintf(stderr, "\nsnews: article %ld found when %ld"
  1167.                     "expected\n", atol(buf+9), g);
  1168.                 exit(1);
  1169.             }
  1170.  
  1171.             /* skip the two eight digit numbers and the 9 and the spaces */
  1172.             buf_p = buf+28;
  1173.             eat_gunk(buf_p);
  1174.  
  1175.             for ( tmp = start; tmp != NULL; ) {
  1176.  
  1177.         cmp = smartcmp(buf_p, tmp->header);
  1178.  
  1179.                 if (cmp > 0) {
  1180.             if (tmp->left)
  1181.                 tmp = tmp->left;
  1182.             else {
  1183.             ptr = &(tmp->left);
  1184.             tmp = NULL;
  1185.             break;
  1186.             }
  1187.         }
  1188.         else if (cmp < 0) {
  1189.             if (tmp->right)
  1190.                 tmp = tmp->right;
  1191.             else {
  1192.             ptr = &(tmp->right);
  1193.             tmp = NULL;
  1194.             break;
  1195.             }
  1196.         }
  1197.         else {
  1198.             /* found this subject */
  1199.              tmp->num_articles++;
  1200.  
  1201.             /* allocate new article number */
  1202.             new = xmalloc(sizeof (ART_ID));
  1203.             new->id = g;
  1204.             new->art_off = atol(buf);
  1205.  
  1206.             /* place it at the end */
  1207.             art_this = tmp->art_num;
  1208.             while (art_this->next_art != NULL) {
  1209.             art_this = art_this->next_art;
  1210.             }
  1211.             art_this->next_art = new;
  1212.  
  1213.             new->last_art = art_this;
  1214.             new->next_art = NULL;
  1215.  
  1216.             break;
  1217.         }
  1218.  
  1219.             }
  1220.  
  1221.             if (tmp == NULL) {
  1222.                 /* not found - allocate new thread */
  1223.  
  1224.                 if (start == NULL) {
  1225.                     start = xmalloc(sizeof (ARTICLE));
  1226.             that = start;
  1227.                     that->last = NULL;
  1228.                 } else {
  1229.                     ct_art++;
  1230.                     that->next = xmalloc(sizeof (ARTICLE));
  1231.                     that->next->last = that;
  1232.                     that = that->next;
  1233.             *ptr = that;
  1234.                 }
  1235.  
  1236.         that->next = NULL;
  1237.         that->index = ct_art;
  1238.         that->left = that->right = NULL;
  1239.  
  1240.                 /* store article data */
  1241.                 that->header = xmalloc(strlen(buf_p) + 1);
  1242.                 strcpy(that->header, buf_p);
  1243.                 that->num_articles = 1;
  1244.  
  1245.                 that->art_num = xmalloc(sizeof (ART_ID));
  1246.                 that->art_num->last_art = NULL;
  1247.                 that->art_num->next_art = NULL;
  1248.                 that->art_num->id = g;
  1249.                 that->art_num->art_off = atol(buf);
  1250.  
  1251.             }
  1252.         }
  1253.  
  1254.         fclose(tmp_file);
  1255.  
  1256.     } else {
  1257.         gotoxy(1,PAGE_SIZE);
  1258.         fprintf(stderr, "\nsnews: can't open index file %s\n", fnx);
  1259.         exit(1);
  1260.     }
  1261.  
  1262.     gp->threads = ct_art + 1;
  1263.  
  1264.     return(start);
  1265. }
  1266.  
  1267.  
  1268.  
  1269. /*------------------------ clean up subject line ----------------------------*/
  1270. void eat_gunk(char *buf)
  1271. {
  1272.     /*
  1273.      *  This routine take the header line, and strips the
  1274.      *  word header word, 'Re:', and any extra blanks
  1275.      */
  1276.  
  1277.     char *p, tmp[MAXLINE];
  1278.  
  1279.     /* strip the Subject: etc off the front */
  1280.     while ( /*(strstr(buf, "Re:") != NULL) ||*/
  1281.         (strnicmp(buf, "From:", 5) == 0) ||
  1282.         (strnicmp(buf, "Path:", 5) == 0) ||
  1283.         (strnicmp(buf, "Subject:", 8) == 0) ||
  1284.         (strnicmp(buf, "Organization:", 13) == 0) ||
  1285.         (strnicmp(buf, "Organisation:", 13) == 0) ||
  1286.         (strnicmp(buf, "Followup-To:", 12) == 0)) {
  1287.         p = strchr(buf, ':');
  1288.         strcpy(buf, p+1);
  1289.     }
  1290.  
  1291.     strcpy(tmp, buf);
  1292.     *buf = '\x00';
  1293.     p = strtok(tmp, " \n\r");
  1294.  
  1295.     while (p != NULL) {
  1296.  
  1297.         if (stricmp(p, "Re:") != 0) {
  1298.             strcat(buf, p);
  1299.             strcat(buf, " ");
  1300.         }
  1301.         p = strtok(NULL, " \n\r");
  1302.     }
  1303.  
  1304.     if (strlen(buf) <= 1)
  1305.         strcpy(buf, "** no subject **");
  1306. }
  1307.  
  1308.  
  1309.  
  1310.  
  1311. /*-------------------- release the subject structures ---------------------*/
  1312. void free_header(ARTICLE *start)
  1313. {
  1314.     /*
  1315.      *  Work our way through the subject structure releasing all the
  1316.      *  memory
  1317.      */
  1318.  
  1319.     ARTICLE *a, *t;
  1320.     ART_ID  *u, *v;
  1321.  
  1322.     a = start;
  1323.  
  1324.     while (a != NULL) {
  1325.  
  1326.         u = a->art_num;
  1327.         while (u != NULL) {
  1328.             v = u->next_art;
  1329.             free(u);
  1330.             u = v;
  1331.         }
  1332.  
  1333.         t = a->next;
  1334.         free(a->header);
  1335.         free(a);
  1336.         a = t;
  1337.     }
  1338. }
  1339.  
  1340.  
  1341. /*------------------- count unread articles in a thread --------------------*/
  1342. int count_unread_in_thread(ACTIVE *gp, ARTICLE *a)
  1343. {
  1344.     /*
  1345.      *  Take the thread 'a' for the given group 'gp' and return the number
  1346.      *  of unread articles
  1347.      */
  1348.  
  1349.     ART_ID *id;
  1350.     int    ct, idx;
  1351.  
  1352.     ct = 0;
  1353.     id = a->art_num;
  1354.  
  1355.     while (id != NULL) {
  1356.         idx = (int)(id->id - gp->lo_num - 1);
  1357.         if (*((gp->read_list)+idx) == FALSE)
  1358.             ct++;
  1359.         id = id->next_art;
  1360.     }
  1361.  
  1362.     return(ct);
  1363. }
  1364.  
  1365.  
  1366.  
  1367. /*------------------- count unread articles in a group --------------------*/
  1368. int count_unread_in_group(ACTIVE *gp)
  1369. {
  1370.     /*
  1371.      *  Take the thread 'a' for the given group 'gp' and return the number
  1372.      *  of unread articles
  1373.      */
  1374.  
  1375.     int articles, ct, i;
  1376.  
  1377.     ct = 0;
  1378.     articles = (int)(gp->hi_num - gp->lo_num);
  1379.  
  1380.     if (gp->read_list)
  1381.         for (i = 0; i < articles; i++) {
  1382.             if (*((gp->read_list)+i) == FALSE)
  1383.                 ct++;
  1384.         }
  1385.  
  1386.     return(ct);
  1387. }
  1388.  
  1389.  
  1390.  
  1391. /*------------------- mark articles as read ---------------------------------*/
  1392. void mark_group_as_read(ACTIVE *gp)
  1393. {
  1394.     /*
  1395.      *  Take the thread 'a' for the given group 'gp' and return the number
  1396.      *  of unread articles
  1397.      */
  1398.  
  1399.     int articles, i, ch;
  1400.  
  1401.     message("Mark all articles in this group as read (y/n)? ");
  1402.     while (((ch = getch()) != 'y') && (ch != 'n') && (ch != ESCAPE));
  1403.  
  1404.     if (ch == 'y') {
  1405.  
  1406.         articles = (int)(gp->hi_num - gp->lo_num);
  1407.  
  1408.         for (i = 0; i < articles; i++)
  1409.             *((gp->read_list)+i) = TRUE;
  1410.  
  1411.     }
  1412.  
  1413. }
  1414.  
  1415.  
  1416. void mark_thread_as_read(ACTIVE *gp, ARTICLE *a)
  1417. {
  1418.     /*
  1419.      *  Take the thread 'a' for the given group 'gp' and return the number
  1420.      *  of unread articles
  1421.      */
  1422.  
  1423.     ART_ID *id;
  1424.     int idx, ch;
  1425.  
  1426.     message("Mark all articles in this thread as read (y/n)? ");
  1427.     while (((ch = getch()) != 'y') && (ch != 'n') && (ch != ESCAPE));
  1428.  
  1429.     if (ch == 'y') {
  1430.  
  1431.         id = a->art_num;
  1432.  
  1433.         while (id != NULL) {
  1434.             idx = (int)(id->id - gp->lo_num - 1);
  1435.             *((gp->read_list)+idx) = TRUE;
  1436.             id = id->next_art;
  1437.         }
  1438.     }
  1439.  
  1440. }
  1441.  
  1442.  
  1443. /*-------------------------- status message ---------------------------------*/
  1444. void message(char *msg)
  1445. {
  1446.     int x;
  1447.  
  1448.     x = _columns / 2 - (strlen(msg)/2);
  1449.     gotoxy(1,PAGE_SIZE);
  1450.     textbackground(LIGHTGRAY);  textcolor(BLACK);
  1451.     printf("%*s%s", x, "", msg);
  1452.     clreol();
  1453.     textbackground(BLACK);  textcolor(LIGHTGRAY);
  1454. }
  1455.  
  1456.  
  1457. /*-------------------------- status message ---------------------------------*/
  1458. void lmessage(char *msg)
  1459. {
  1460.     gotoxy(1,PAGE_SIZE);
  1461.     textbackground(LIGHTGRAY);  textcolor(BLACK);
  1462.     printf("%s", msg);
  1463.     clreol();
  1464.     textbackground(BLACK);  textcolor(LIGHTGRAY);
  1465. }
  1466.