home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 3 / 3197 < prev    next >
Encoding:
Internet Message Format  |  1991-04-18  |  41.7 KB

  1. From: skrenta@blekko.commodore.com (Rich Skrenta)
  2. Newsgroups: alt.sources
  3. Subject: Tass 3.2 newsreader part 1/3
  4. Message-ID: <159@blekko.commodore.com>
  5. Date: 17 Apr 91 17:00:36 GMT
  6.  
  7.  
  8. # This is a shell archive.  Remove anything before this line,
  9. # then unpack it by saving it in a file and typing "sh file".
  10. #
  11. # This archive contains:
  12. #    COPYRIGHT    Makefile    README        Tass.man    
  13. #    art.c        curses.c    group.c        hashstr.c    
  14. #
  15.  
  16. echo x - COPYRIGHT
  17. cat >COPYRIGHT <<'@EOF'
  18. /*
  19.  *  Tass, a visual Usenet news reader
  20.  *  (c) Copyright 1990 by Rich Skrenta
  21.  *
  22.  *  Distribution agreement:
  23.  *
  24.  *    You may freely copy or redistribute this software, so long
  25.  *    as there is no profit made from its use, sale, trade or
  26.  *    reproduction.  You may not change this copyright notice,
  27.  *    and it must be included prominently in any copy made.
  28.  */
  29. @EOF
  30.  
  31. chmod 600 COPYRIGHT
  32.  
  33. echo x - Makefile
  34. cat >Makefile <<'@EOF'
  35.  
  36. #  Make sure LIBDIR, SPOOLDIR and MAILER are correct in tass.h
  37. #
  38. #  Make sure spool_open.c knows whether readdir returns struct direct or
  39. #  struct dirent.  The defines below should take care of this.
  40.  
  41. #  For Berkeley systems:
  42. #
  43. # CFLAGS= -DBSD
  44. # LIBS= -lcurses -ltermcap
  45.  
  46. #  For System V
  47. #
  48. CFLAGS=-g 
  49. LIBS= -lcurses
  50.  
  51. #  For 286 Xenix
  52. #
  53. # CFLAGS=-g -M2l -F 8000
  54. # LIBS= -lcurses -ltermcap -lx
  55.  
  56. #  For SCO Unix System V
  57. #
  58. # CFLAGS=-g -UM_XENIX -DSCO_UNIX
  59. # LIBS= -lcurses -lgen
  60.  
  61.  
  62. #  You only need to worry about the following two defines if you want
  63. #  to build rtass (remote Tass via nntp)
  64. #
  65. # point NNTPLIB at the nntp clientlib.o support library
  66. #
  67. NNTPLIB=../nntp/common/clientlib.o
  68. #
  69. #  NETLIBS should be the networking libraries you need to link with
  70. #  the nntp clientlib.o
  71. #
  72. NETLIBS=-lnsl -lsocket
  73.  
  74.  
  75. OBJECTS    =    curses.o art.o group.o hashstr.o mail.o main.o misc.o \
  76.         page.o prompt.o screen.o select.o time.o
  77.  
  78. tass: $(OBJECTS) spool_open.o
  79.     cc $(CFLAGS) -o tass $(OBJECTS) spool_open.o $(LIBS)
  80.  
  81. rtass: $(OBJECTS) nntp_open.o
  82.     cc $(CFLAGS) -o rtass $(OBJECTS) nntp_open.o $(LIBS) $(NNTPLIB) $(NETLIBS)
  83.  
  84. shar:
  85.     -mv -f ../tass.shar ../tass.shar-
  86.     shar -v [A-Z]* *.[ch] > ../tass.shar
  87.  
  88. clean:
  89.     rm -f *.o
  90.  
  91. clobber: clean
  92.     rm -f tass rtass
  93.  
  94.  
  95. art.o:        art.c tass.h
  96. curses.o:    curses.c
  97. group.o:    group.c tass.h
  98. hashstr.o:    hashstr.c
  99. mail.o:        mail.c
  100. main.o:        main.c tass.h
  101. misc.o:        misc.c tass.h
  102. nntp_open.o:    nntp_open.c tass.h nntp.h
  103. page.o:        page.c tass.h
  104. prompt.o:    prompt.c tass.h
  105. screen.o:    screen.c tass.h
  106. select.o:    select.c tass.h
  107. spool_open.o:    spool_open.c tass.h
  108. time.o:        time.c
  109. @EOF
  110.  
  111. chmod 644 Makefile
  112.  
  113. echo x - README
  114. cat >README <<'@EOF'
  115. Tass is a full screen threaded newsreader.
  116.  
  117.     o Organizes articles by threads.  Displays a really nice
  118.       article selection page.
  119.  
  120.     o Group selection page makes it easy to scan newsgroups, subscribe,
  121.       unsubscribe, reorder your .newsrc
  122.  
  123.     o If you've ever used Notes, this is the program for you.
  124.       Tass looks a lot like Notes, but has a few improvements:
  125.         visual group selection page, Notes didn't have one
  126.         rn style unread article detection as opposed to single timeline
  127.         uses standard /usr/spool/news article layout
  128.  
  129. I wrote Tass because I "learned" the Usenet on Notes and couldn't stand rn.
  130. No rn flames here, but if you've wished you could view the news a different
  131. way, try Tass.
  132.  
  133. Newsreading style under Tass tends to be different than with rn.  Instead of
  134. plowing through each group reading everything unread, you may find yourself
  135. reading fewer articles in more groups.  It's easier to skip about and only
  136. read interesting threads with Tass.
  137.  
  138. Tass keeps an index file for each group.  The first time you enter a group,
  139. it will be a bit slow creating this file.  After that Tass will incrementally
  140. update the index file and there should be little delay.
  141.  
  142. You can also run Tass in "update mode" out of cron to update the indexes.
  143.  
  144.  
  145. Building Tass
  146. -------- ----
  147.  
  148.     1)  Edit the Makefile.  Select CFLAGS and LIBS for your system.
  149.     2)  Edit tass.h.  Make sure that LIBDIR, SPOOLDIR, MAILER and
  150.         DEF_EDITOR are correct for your system.
  151.     3)  'make'
  152.  
  153. To build the remote NNTP Tass (rtass) you will need the nntp sources;
  154. specifically, clientlib.o.  Point the Makefile variable NNTPLIB at your
  155. clientlib.o and say 'make rtass'.
  156.  
  157.  
  158. Installing Tass
  159. ---------- ----
  160.  
  161. Copy the tass executable to some useful place.  If you make tass setuid news,
  162. it will keep the indexes in the news spool directory.  If not, tass will hide
  163. indexes in the user's home directory.
  164.  
  165. Don't make rtass setuid news since it will get articles from the NNTP host
  166. and not from /usr/spool/news.
  167.  
  168. There is a brief man page (Tass.man) which may be copied to the appropriate
  169. man directory.
  170.  
  171.  
  172. NOTE:
  173. -----
  174.     Tass 3.2 uses a different name for the index files.  If you've been
  175.     using Tass 3.0 or 3.1, you should remove these old indexes before
  176.     running 3.2.  Do this with
  177.  
  178.         rm -rf $HOME/.tindex
  179.        or
  180.         find /usr/spool/news -name '.tindex' -exec rm {} \;
  181.  
  182.  
  183. Changes from 3.0
  184. ------- ---- ---
  185.  
  186.     o Index files are now 1/2 to 1/3 their previous size
  187.     o Tass is much more conservative in its memory usage
  188.     o Tass recognizes .signature and .Sig files
  189.     o Screen updating is more efficient in many places
  190.     o Job control fixed for BSD systems
  191.     o The various mailing commands should work much better
  192.     o Author search
  193.     o Support for NNTP (rtass)
  194.     o Many other enhancements
  195.  
  196.  
  197. Rich Skrenta
  198. -- 
  199. skrenta@blekko.commodore.com
  200.  
  201. @EOF
  202.  
  203. chmod 644 README
  204.  
  205. echo x - Tass.man
  206. cat >Tass.man <<'@EOF'
  207. .TH TASS 1A
  208. .SH NAME
  209. tass, rtass \- Visual threaded Usenet news reader
  210. .SH SYNOPSIS
  211. .nf
  212. tass [options] [newsgroups]
  213. rtass [options] [newsgroups]
  214. .fi
  215. .SH DESCRIPTION
  216. Tass is a full screen threaded Usenet newsreader.
  217. Tass has three newsreading levels:
  218. the newsgroup selection page, the group index page and the article viewer.
  219. Use the 'h' (help) command to view a list of the commands available at a
  220. particular level.
  221. .PP
  222. On startup Tass will show a list of the newsgroups found in $HOME/.newsrc.
  223. An arrow will point to the first newsgroup.  Move the arrow by either using
  224. the terminal arrow keys or 'j' and 'k'.  Control-D will page down, control-U
  225. will page up.  Enter a newsgroup by pressing RETURN.
  226. .PP
  227. The TAB key may be used to advance to the next newsgroup with unread articles
  228. and enter it.
  229. .PP
  230. rtass will attempt to connect to the NNTP port on the machine named in the
  231. environment variable NNTPSERVER or contained in the file /etc/nntpserver.
  232. rtass will index somewhat slower because the articles must be retrieved
  233. via the NNTP protocol.
  234. .PP
  235. Refer to the Tass help screens for further commands.
  236. .SH TASS INDEX FILES
  237. In order to keep track of threads, Tass maintains an index for each group.
  238. If Tass is made setuid to news, the indexes will be stored in the news spool
  239. directory (typically /usr/spool/news).  If
  240. Tass is not setuid, it will store
  241. index files in the user's home directory, in a subdirectory called .tindx.
  242. .PP
  243. Entering a group the first time tends to be slow because the index file must
  244. be built from scratch.  Subsequent readings of a group will cause
  245. Tass to incrementally update the index file, adding or removing entries as new
  246. articles come in or as news expires.
  247. .PP
  248. Tass may be run in update mode (the -u option) to update a series of groups
  249. at one time.  tass -u is usually run from cron.
  250. .PP
  251. Do not make rtass setuid news since news will be obtained via NNTP and not
  252. from /usr/spool/news.
  253. .SH SIGNATURES
  254. Tass will recognize a signature in either $HOME/.signature or $HOME/.Sig.
  255. If .signature exists, then the signature will be pulled into the editor
  256. for Tass mail commands.  A signature in .signature will not be pulled
  257. into the editor for posting commands since the inews program
  258. will append the signature itself.
  259. .PP
  260. A signature in .Sig will be pulled into the editor for both posting
  261. and mailing commands.
  262. .SH OPTIONS
  263. .I Tass
  264. recognizes the following options:
  265. .TP
  266. -f file
  267. Use the indicated file in place of $HOME/.newsrc.
  268. .TP
  269. -u
  270. Run Tass in update mode.  Tass will make indexes current for every group
  271. in its newsrc.
  272.  
  273. A good way to keep Tass index files current is to run tass -u from cron:
  274.  
  275. .nf
  276. 20 6 * * *    /usr/local/bin/tass -u -f /usr/lib/news/tass_groups
  277. .fi
  278.  
  279. This would update the index files for those groups appearing in
  280. /usr/lib/news/tass_groups.  To index all of the groups on the system,
  281. run tass -u with -f indicating the active file:
  282.  
  283. .nf
  284. 20 6 * * *    /usr/local/bin/tass -u -f /usr/lib/news/active
  285. .fi
  286.  
  287. .SH AUTHOR
  288. .nf
  289. Rich Skrenta
  290. skrenta@blekko.commodore.com or skrenta@blekko.uucp.
  291. .fi
  292. @EOF
  293.  
  294. chmod 644 Tass.man
  295.  
  296. echo x - art.c
  297. cat >art.c <<'@EOF'
  298.  
  299.  
  300. #include    <stdio.h>
  301. #include    <ctype.h>
  302. #include    <signal.h>
  303. #include    "tass.h"
  304.  
  305.  
  306. char index_file[LEN+1];
  307. char *glob_art_group;
  308. extern char *hash_str();
  309.  
  310.  
  311. #ifdef SIGTSTP
  312. void
  313. art_susp(i)
  314. int i;
  315. {
  316.  
  317.     Raw(FALSE);
  318.     putchar('\n');
  319.     signal(SIGTSTP, SIG_DFL);
  320. #ifdef BSD
  321.         sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
  322. #endif
  323.     kill(0, SIGTSTP);
  324.  
  325.     signal(SIGTSTP, art_susp);
  326.     Raw(TRUE);
  327.  
  328.     mail_setup();
  329.     ClearScreen();
  330.     MoveCursor(LINES, 0);
  331.     printf("Group %s...    ", glob_art_group);
  332.     fflush(stdout);
  333. }
  334. #endif
  335.  
  336.  
  337. /*
  338.  *  Convert a string to a long, only look at first n characters
  339.  */
  340.  
  341. my_atol(s, n)
  342. char *s;
  343. int n;
  344. {
  345.     long ret = 0;
  346.  
  347.     while (*s && n--) {
  348.         if (*s >= '0' && *s <= '9')
  349.             ret = ret * 10 + (*s - '0');
  350.         else
  351.             return -1;
  352.         s++;
  353.     }
  354.  
  355.     return ret;
  356. }
  357.  
  358.  
  359. /*
  360.  *  Construct the pointers to the basenotes of each thread
  361.  *  arts[] contains every article in the group.  inthread is
  362.  *  set on each article that is after the first article in the
  363.  *  thread.  Articles which have been expired have their thread
  364.  *  set to -2.
  365.  */
  366.  
  367. find_base() {
  368.     int i;
  369.  
  370.     top_base = 0;
  371.  
  372.     for (i = 0; i < top; i++)
  373.         if (!arts[i].inthread && arts[i].thread != -2) {
  374.             if (top_base >= max_art)
  375.                 expand_art();
  376.             base[top_base++] = i;
  377.         }
  378. }
  379.  
  380.  
  381. /* 
  382.  *  Count the number of non-expired articles in arts[]
  383.  */
  384.  
  385. num_arts() {
  386.     int sum = 0;
  387.  
  388.     int i;
  389.  
  390.     for (i = 0; i < top; i++)
  391.         if (arts[i].thread != -2)
  392.             sum++;
  393.  
  394.     return sum;
  395. }
  396.  
  397.  
  398. /*
  399.  *  Do we have an entry for article art?
  400.  */
  401.  
  402. valid_artnum(art)
  403. long art;
  404. {
  405.     int i;
  406.  
  407.     for (i = 0; i < top; i++)
  408.         if (arts[i].artnum == art)
  409.             return i;
  410.  
  411.     return -1;
  412. }
  413.  
  414.  
  415. /*
  416.  *  Return TRUE if arts[] contains any expired articles
  417.  *  (articles we have an entry for which don't have a corresponding
  418.  *   article file in the spool directory)
  419.  */
  420.  
  421. purge_needed() {
  422.     int i;
  423.  
  424.     for (i = 0; i < top; i++)
  425.         if (arts[i].thread == -2)
  426.             return TRUE;
  427.  
  428.     return FALSE;
  429. }
  430.  
  431.  
  432. /*
  433.  *  Main group indexing routine.  Group should be the name of the
  434.  *  newsgroup, i.e. "comp.unix.amiga".  group_path should be the
  435.  *  same but with the .'s turned into /'s: "comp/unix/amiga"
  436.  *
  437.  *  Will read any existing index, create or incrementally update
  438.  *  the index by looking at the articles in the spool directory,
  439.  *  and attempt to write a new index if necessary.
  440.  */
  441.  
  442. index_group(group, group_path)
  443. char *group;
  444. char *group_path;
  445. {
  446.     int modified;
  447.  
  448.     glob_art_group = group;
  449.  
  450. #ifdef SIGTSTP
  451.     signal(SIGTSTP, art_susp);
  452. #endif
  453.  
  454.     if (!update) {
  455.         clear_message();
  456.         MoveCursor(LINES, 0);
  457.         printf("Group %s...    ", group);
  458.         fflush(stdout);
  459.     }
  460.  
  461.     hash_reclaim();
  462.     if (local_index)
  463.         find_local_index(group);
  464.     else
  465.         sprintf(index_file, "%s/%s/.tindx", SPOOLDIR, group_path);
  466.  
  467.     load_index();
  468.     modified = read_group(group, group_path);
  469.     make_threads();
  470.     if (modified || purge_needed()) {
  471.         if (local_index) {    /* writing index in home directory */
  472.             setuid(real_uid);    /* so become them */
  473.             setgid(real_gid);
  474.         }
  475.         dump_index(group);
  476.         if (local_index) {
  477.             setuid(tass_uid);
  478.             setgid(tass_gid);
  479.         }
  480.     }
  481.     find_base();
  482.  
  483.     if (modified && !update)
  484.         clear_message();
  485. }
  486.  
  487.  
  488. /*
  489.  *  Index a group.  Assumes any existing index has already been
  490.  *  loaded.
  491.  */
  492.  
  493. read_group(group, group_path)
  494. char *group;
  495. char *group_path;
  496. {
  497.     int fd;
  498.     long art;
  499.     int count;
  500.     int modified = FALSE;
  501.     int respnum;
  502.     int i;
  503.  
  504.     setup_base(group, group_path);      /* load article numbers into base[] */
  505.     count = 0;
  506.  
  507.     for (i = 0; i < top_base; i++) {    /* for each article # */
  508.         art = base[i];
  509.  
  510. /*
  511.  *  Do we already have this article in our index?  Change thread from
  512.  *  -2 to -1 if so and skip the header eating.
  513.  */
  514.  
  515.         if ((respnum = valid_artnum(art)) >= 0) {
  516.             arts[respnum].thread = -1;
  517.             arts[respnum].unread = 1;
  518.             continue;
  519.         }
  520.  
  521.         if (!modified)
  522.             modified = TRUE;   /* we've modified the index */
  523.                        /* it will need to be re-written */
  524.  
  525.         fd = open_header_fd(group_path, art);
  526.         if (fd < 0)
  527.             continue;
  528.  
  529. /*
  530.  *  Add article to arts[]
  531.  */
  532.  
  533.         if (top >= max_art)
  534.             expand_art();
  535.  
  536.         arts[top].artnum = art;
  537.         arts[top].thread = -1;
  538.         arts[top].inthread = FALSE;
  539.         arts[top].unread = 1;
  540.  
  541.         if (!parse_headers(fd, &arts[top]))
  542.             continue;
  543.         top++;
  544.         close(fd);
  545.  
  546.         if (++count % 10 == 0 && !update) {
  547.             printf("\b\b\b\b%4d", count);
  548.             fflush(stdout);
  549.         }
  550.     }
  551.  
  552.     return modified;
  553. }
  554.  
  555.  
  556. /*
  557.  *  Go through the articles in arts[] and use .thread to snake threads
  558.  *  through them.  Use the subject line to construct threads.  The
  559.  *  first article in a thread should have .inthread set to FALSE, the
  560.  *  rest TRUE.  Only do unexprired articles we haven't visited yet
  561.  *  (arts[].thread == -1).
  562.  */
  563.  
  564. make_threads() {
  565.     int i;
  566.     int j;
  567.  
  568.     for (i = 0; i < top; i++) {
  569.         if (arts[i].thread == -1)
  570.             for (j = i+1; j < top; j++)
  571.             if (arts[j].thread != -2
  572.             &&  arts[i].subject == arts[j].subject) {
  573.                 arts[i].thread = j;
  574.                 arts[j].inthread = TRUE;
  575.                 break;
  576.             }
  577.     }
  578. }
  579.  
  580.  
  581. /*
  582.  *  Return a pointer into s eliminating any leading Re:'s.  Example:
  583.  *
  584.  *      Re: Reorganization of misc.jobs
  585.  *      ^   ^
  586.  */
  587.  
  588. char *
  589. eat_re(s)
  590. char *s;
  591. {
  592.  
  593.     while (*s == 'r' || *s == 'R') {
  594.         if ((*(s+1) == 'e' || *(s+1) == 'E')) {
  595.             if (*(s+2) == ':')
  596.                 s += 3;
  597.             else if (*(s+2) == '^' && isdigit(*(s+3)) && *(s+4) == ':')
  598.                 s += 5;            /* hurray nn */
  599.             else
  600.                 break;
  601.         } else
  602.             break;
  603.         while (*s == ' ')
  604.             s++;
  605.     }
  606.  
  607.     return s;
  608. }
  609.  
  610.  
  611. parse_headers(fd, h)
  612. int fd;
  613. struct header *h;
  614. {
  615.     char buf[1024];
  616.     char *p, *q;
  617.     char flag;
  618.     int n;
  619.     char buf2[1024];
  620.     char *s;
  621.  
  622.     n = read(fd, buf, 1024);
  623.     if (n <= 0)
  624.         return FALSE;
  625.  
  626.     buf[n - 1] = '\0';
  627.  
  628.     h->subject = "<no subject>";
  629.     h->from = "<no from>";
  630.  
  631.     p = buf;
  632.     while (1) {
  633.         for (q = p; *p && *p != '\n'; p++)
  634.             if (((*p) & 0x7F) < 32)
  635.                 *p = ' ';
  636.         flag = *p;
  637.         *p++ = '\0';
  638.  
  639.         if (strncmp(q, "From: ", 6) == 0) {
  640.             strncpy(buf2, &q[6], MAX_FROM-1);
  641.             buf2[MAX_FROM-1] = '\0';
  642.             h->from = hash_str(buf2);
  643.         } else if (strncmp(q, "Subject: ", 9) == 0) {
  644.             strcpy(buf2, &q[9]);
  645.             s = eat_re(buf2);
  646.             s[MAX_SUBJ-1] = '\0';
  647.             h->subject = hash_str(eat_re(s));
  648.         }
  649.  
  650.         if (!flag || *p == '\n')
  651.             break;
  652.     }
  653.  
  654.     return TRUE;
  655. }
  656.  
  657.  
  658. /* 
  659.  *  Write out a .tindx file.  Write the group name first so if
  660.  *  local indexing is done we can disambiguate between group name
  661.  *  hash collisions by looking at the index file.
  662.  */
  663.  
  664. dump_index(group)
  665. char *group;
  666. {
  667.     int i;
  668.     char buf[200];
  669.     char nam[200];
  670.     FILE *fp;
  671.     int *iptr;
  672.     int realnum;
  673.  
  674.     sprintf(nam, "%s.%d", index_file, getpid());
  675.     fp = fopen(nam, "w");
  676.  
  677.     if (fp == NULL)
  678.         return;
  679.  
  680.     fprintf(fp, "%s\n", group);
  681.     fprintf(fp, "%d\n", num_arts());
  682.  
  683.     realnum = 0;
  684.     for (i = 0; i < top; i++)
  685.         if (arts[i].thread != -2) {
  686.             fprintf(fp, "%ld\n", arts[i].artnum);
  687.  
  688.             iptr = (int *) arts[i].subject;
  689.             iptr--;
  690.  
  691.             if (*iptr < 0) {
  692.                 fprintf(fp, " %s\n", arts[i].subject);
  693.                 *iptr = realnum;
  694.             } else
  695.                 fprintf(fp, "%%%d\n", *iptr);
  696.  
  697.             iptr = (int *) arts[i].from;
  698.             iptr--;
  699.  
  700.             if (*iptr < 0) {
  701.                 fprintf(fp, " %s\n", arts[i].from);
  702.                 *iptr = realnum;
  703.             } else
  704.                 fprintf(fp, "%%%d\n", *iptr);
  705.  
  706.             realnum++;
  707.         }
  708.  
  709.     fclose(fp);
  710.     chmod(nam, 0644);
  711.     unlink(index_file);
  712.     link(nam, index_file);
  713.     unlink(nam);
  714. }
  715.  
  716.  
  717. /*
  718.  *  strncpy that stops at a newline and null terminates
  719.  */
  720.  
  721. my_strncpy(p, q, n)
  722. char *p;
  723. char *q;
  724. int n;
  725. {
  726.  
  727.     while (n--) {
  728.         if (!*q || *q == '\n')
  729.             break;
  730.         *p++ = *q++;
  731.     }
  732.     *p = '\0';
  733. }
  734.  
  735.  
  736. /*
  737.  *  Read in a .tindx file.
  738.  */
  739.  
  740. load_index()
  741. {
  742.     int i;
  743.     long j;
  744.     char buf[200];
  745.     FILE *fp;
  746.     int first = TRUE;
  747.     char *p;
  748.     int n;
  749.     char *err;
  750.  
  751.     top = 0;
  752.  
  753.     fp = fopen(index_file, "r");
  754.     if (fp == NULL)
  755.         return;
  756.  
  757.     if (fgets(buf, 200, fp) == NULL
  758.     ||  fgets(buf, 200, fp) == NULL) {
  759.         err = "one";
  760.         goto corrupt_index;
  761.     }
  762.  
  763.     i = atol(buf);
  764.     while (top < i) {
  765.         if (top >= max_art)
  766.             expand_art();
  767.  
  768.         arts[top].thread = -2;
  769.         arts[top].inthread = FALSE;
  770.  
  771.         if (fgets(buf, 200, fp) == NULL) {
  772.             err = "two";
  773.             goto corrupt_index;
  774.         }
  775.         arts[top].artnum = atol(buf);
  776.  
  777.         if (fgets(buf, 200, fp) == NULL) {
  778.             err = "three";
  779.             goto corrupt_index;
  780.         }
  781.  
  782.         if (buf[0] == '%') {
  783.             n = atoi(&buf[1]);
  784.             if (n >= top || n < 0) {
  785.                 err = "eight";
  786.                 goto corrupt_index;
  787.             }
  788.             arts[top].subject = arts[n].subject;
  789.         } else if (buf[0] == ' ') {
  790.             for (p = &buf[1]; *p && *p != '\n'; p++) ;
  791.             *p = '\0';
  792.             buf[MAX_SUBJ] = '\0';
  793.             arts[top].subject = hash_str(&buf[1]);
  794.         } else {
  795.             err = "six";
  796.             goto corrupt_index;
  797.         }
  798.                 
  799.         if (fgets(buf, 200, fp) == NULL) {
  800.             err = "four";
  801.             goto corrupt_index;
  802.         }
  803.  
  804.         if (buf[0] == '%') {
  805.             n = atoi(&buf[1]);
  806.             if (n >= top || n < 0) {
  807.                 err = "nine";
  808.                 goto corrupt_index;
  809.             }
  810.             arts[top].from = arts[n].from;
  811.         } else if (buf[0] == ' ') {
  812.             for (p = &buf[1]; *p && *p != '\n'; p++) ;
  813.             *p = '\0';
  814.             buf[MAX_FROM] = '\0';
  815.             arts[top].from = hash_str(&buf[1]);
  816.         } else {
  817.             err = "seven";
  818.             goto corrupt_index;
  819.         }
  820.  
  821.         top++;
  822.     }
  823.  
  824.     fclose(fp);
  825.     return;
  826.  
  827. corrupt_index:
  828.     fprintf(stderr, "\r\n%s: index file %s corrupt, top=%d\r\n",
  829.                         err, index_file, top);
  830.     unlink(index_file);
  831.     top = 0;
  832. }
  833.  
  834.  
  835. /*
  836.  *  Look in the local $HOME/.tindx (or wherever) directory for the
  837.  *  index file for the given group.  Hashing the group name gets
  838.  *  a number.  See if that #.1 file exists; if so, read first line.
  839.  *  Group we want?  If no, try #.2.  Repeat until no such file or
  840.  *  we find an existing file that matches our group.
  841.  */
  842.  
  843. find_local_index(group)
  844. char *group;
  845. {
  846.     unsigned long h;
  847.     static char buf[200];
  848.     int i;
  849.     char *p;
  850.     FILE *fp;
  851.  
  852.     h = hash_groupname(group);
  853.  
  854.     i = 1;
  855.     while (1) {
  856.         sprintf(index_file, "%s/%lu.%d", indexdir, h, i);
  857.         fp = fopen(index_file, "r");
  858.         if (fp == NULL)
  859.             return;
  860.  
  861.         if (fgets(buf, 200, fp) == NULL) {
  862.             fclose(fp);
  863.             return;
  864.         }
  865.         fclose(fp);
  866.  
  867.         for (p = buf; *p && *p != '\n'; p++) ;
  868.         *p = '\0';
  869.  
  870.         if (strcmp(buf, group) == 0)
  871.             return;
  872.  
  873.         i++;
  874.     }
  875. }
  876.  
  877.  
  878. /*
  879.  *  Run the index file updater only for the groups we've loaded.
  880.  */
  881.  
  882. do_update() {
  883.     int i;
  884.     char group_path[200];
  885.     char *p;
  886.  
  887.     for (i = 0; i < local_top; i++) {
  888.         strcpy(group_path, active[my_group[i]].name);
  889.         for (p = group_path; *p; p++)
  890.             if (*p == '.')
  891.                 *p = '/';
  892.  
  893.         index_group(active[my_group[i]].name, group_path);
  894.     }
  895. }
  896.  
  897. @EOF
  898.  
  899. chmod 644 art.c
  900.  
  901. echo x - curses.c
  902. cat >curses.c <<'@EOF'
  903.  
  904. /*
  905.  *  This is a screen management library borrowed with permission from the
  906.  *  Elm mail system (a great mailer--I highly recommend it!).
  907.  *
  908.  *  I've hacked this library to only provide what Tass needs.
  909.  *
  910.  *  Original copyright follows:
  911.  */
  912.  
  913. /*******************************************************************************
  914.  *  The Elm Mail System  -  $Revision: 2.1 $   $State: Exp $
  915.  *
  916.  *             Copyright (c) 1986 Dave Taylor
  917.  ******************************************************************************/
  918.  
  919. #include <stdio.h>
  920. #include <curses.h>
  921.  
  922. #define        TRUE        1
  923. #define        FALSE        0
  924.  
  925. #define        BACKSPACE    '\b'
  926. #define        VERY_LONG_STRING    2500
  927.  
  928. int LINES=23;
  929. int COLS=80;
  930.  
  931. int inverse_okay = TRUE;
  932.  
  933. /*
  934. #ifdef BSD
  935. #  ifndef BSD4_1
  936. #    include <sgtty.h>
  937. #  else
  938. #    include <termio.h>
  939. #  endif
  940. # else
  941. #  include <termio.h>
  942. #endif
  943. */
  944.  
  945. #include <ctype.h>
  946.  
  947. /*
  948. #ifdef BSD
  949. #undef tolower
  950. #endif
  951. */
  952.  
  953. #define TTYIN    0
  954.  
  955. #ifdef SHORTNAMES
  956. # define _clearinverse    _clrinv
  957. # define _cleartoeoln    _clrtoeoln
  958. # define _cleartoeos    _clr2eos
  959. #endif
  960.  
  961. #ifndef BSD
  962. struct termio _raw_tty, 
  963.               _original_tty;
  964. #else
  965. #define TCGETA    TIOCGETP
  966. #define TCSETAW    TIOCSETP
  967.  
  968. struct sgttyb _raw_tty,
  969.           _original_tty;
  970. #endif
  971.  
  972. static int _inraw = 0;                  /* are we IN rawmode?    */
  973.  
  974. #define DEFAULT_LINES_ON_TERMINAL    24
  975. #define DEFAULT_COLUMNS_ON_TERMINAL    80
  976.  
  977. static int _memory_locked = 0;        /* are we IN memlock??   */
  978.  
  979. static int _intransmit;            /* are we transmitting keys? */
  980.  
  981. static
  982. char *_clearscreen, *_moveto, *_cleartoeoln, *_cleartoeos,
  983.     *_setinverse, *_clearinverse;
  984.  
  985. static
  986. int _lines,_columns;
  987.  
  988. static char _terminal[1024];              /* Storage for terminal entry */
  989. static char _capabilities[1024];           /* String for cursor motion */
  990.  
  991. static char *ptr = _capabilities;    /* for buffering         */
  992.  
  993. int    outchar();            /* char output for tputs */
  994. char  *tgetstr(),                    /* Get termcap capability */
  995.       *tgoto();                /* and the goto stuff    */
  996.  
  997. InitScreen()
  998. {
  999. int  tgetent(),      /* get termcap entry */
  1000.      err;
  1001. char termname[40];
  1002. char *strcpy(), *getenv();
  1003.     
  1004.     if (getenv("TERM") == NULL) {
  1005.         fprintf(stderr,
  1006.           "TERM variable not set; Tass requires screen capabilities\n");
  1007.         return(FALSE);
  1008.     }
  1009.     if (strcpy(termname, getenv("TERM")) == NULL) {
  1010.         fprintf(stderr,"Can't get TERM variable\n");
  1011.         return(FALSE);
  1012.     }
  1013.     if ((err = tgetent(_terminal, termname)) != 1) {
  1014.         fprintf(stderr,"Can't get entry for TERM\n");
  1015.         return(FALSE);
  1016.     }
  1017.  
  1018.     /* load in all those pesky values */
  1019.     _clearscreen       = tgetstr("cl", &ptr);
  1020.     _moveto            = tgetstr("cm", &ptr);
  1021.     _cleartoeoln       = tgetstr("ce", &ptr);
  1022.     _cleartoeos        = tgetstr("cd", &ptr);
  1023.     _lines                 = tgetnum("li");
  1024.     _columns       = tgetnum("co");
  1025.     _setinverse        = tgetstr("so", &ptr);
  1026.     _clearinverse      = tgetstr("se", &ptr);
  1027.  
  1028.     if (!_clearscreen) {
  1029.         fprintf(stderr,
  1030.             "Terminal must have clearscreen (cl) capability\n");
  1031.         return(FALSE);
  1032.     }
  1033.     if (!_moveto) {
  1034.         fprintf(stderr,
  1035.             "Terminal must have cursor motion (cm)\n");
  1036.         return(FALSE);
  1037.     }
  1038.     if (!_cleartoeoln) {
  1039.         fprintf(stderr,
  1040.             "Terminal must have clear to end-of-line (ce)\n");
  1041.         return(FALSE);
  1042.     }
  1043.     if (!_cleartoeos) {
  1044.         fprintf(stderr,
  1045.             "Terminal must have clear to end-of-screen (cd)\n");
  1046.         return(FALSE);
  1047.     }
  1048.     if (_lines == -1)
  1049.         _lines = DEFAULT_LINES_ON_TERMINAL;
  1050.     if (_columns == -1)
  1051.         _columns = DEFAULT_COLUMNS_ON_TERMINAL;
  1052.     return(TRUE);
  1053. }
  1054.  
  1055. ScreenSize(lines, columns)
  1056. int *lines, *columns;
  1057. {
  1058.     /** returns the number of lines and columns on the display. **/
  1059.  
  1060.     if (_lines == 0) _lines = DEFAULT_LINES_ON_TERMINAL;
  1061.     if (_columns == 0) _columns = DEFAULT_COLUMNS_ON_TERMINAL;
  1062.  
  1063.     *lines = _lines - 1;        /* assume index from zero*/
  1064.     *columns = _columns;        /* assume index from one */
  1065. }
  1066.  
  1067. ClearScreen()
  1068. {
  1069.     /* clear the screen: returns -1 if not capable */
  1070.  
  1071.     tputs(_clearscreen, 1, outchar);
  1072.     fflush(stdout);      /* clear the output buffer */
  1073. }
  1074.  
  1075. MoveCursor(row, col)
  1076. int row, col;
  1077. {
  1078.     /** move cursor to the specified row column on the screen.
  1079.             0,0 is the top left! **/
  1080.  
  1081.     char *stuff, *tgoto();
  1082.  
  1083.     stuff = tgoto(_moveto, col, row);
  1084.     tputs(stuff, 1, outchar);
  1085.     fflush(stdout);
  1086. }
  1087.  
  1088. CleartoEOLN()
  1089. {
  1090.     /** clear to end of line **/
  1091.  
  1092.     tputs(_cleartoeoln, 1, outchar);
  1093.     fflush(stdout);  /* clear the output buffer */
  1094. }
  1095.  
  1096. CleartoEOS()
  1097. {
  1098.     /** clear to end of screen **/
  1099.  
  1100.     tputs(_cleartoeos, 1, outchar);
  1101.     fflush(stdout);  /* clear the output buffer */
  1102. }
  1103.  
  1104. StartInverse()
  1105. {
  1106.     /** set inverse video mode **/
  1107.  
  1108.     if (_setinverse && inverse_okay)
  1109.         tputs(_setinverse, 1, outchar);
  1110. /*    fflush(stdout);    */
  1111. }
  1112.  
  1113.  
  1114. EndInverse()
  1115. {
  1116.     /** compliment of startinverse **/
  1117.  
  1118.     if (_clearinverse && inverse_okay)
  1119.         tputs(_clearinverse, 1, outchar);
  1120. /*    fflush(stdout);    */
  1121. }
  1122.  
  1123. RawState()
  1124. {
  1125.     /** returns either 1 or 0, for ON or OFF **/
  1126.  
  1127.     return( _inraw );
  1128. }
  1129.  
  1130. Raw(state)
  1131. int state;
  1132. {
  1133.     /** state is either TRUE or FALSE, as indicated by call **/
  1134.  
  1135.     if (state == FALSE && _inraw) {
  1136.       (void) ioctl(TTYIN, TCSETAW, &_original_tty);
  1137.       _inraw = 0;
  1138.     }
  1139.     else if (state == TRUE && ! _inraw) {
  1140.  
  1141.       (void) ioctl(TTYIN, TCGETA, &_original_tty);    /** current setting **/
  1142.  
  1143.       (void) ioctl(TTYIN, TCGETA, &_raw_tty);    /** again! **/
  1144. #ifdef BSD
  1145.       _raw_tty.sg_flags &= ~(ECHO | CRMOD);    /* echo off */
  1146.       _raw_tty.sg_flags |= CBREAK;    /* raw on    */
  1147. #else
  1148.       _raw_tty.c_lflag &= ~(ICANON | ECHO);    /* noecho raw mode        */
  1149.  
  1150.       _raw_tty.c_cc[VMIN] = '\01';    /* minimum # of chars to queue    */
  1151.       _raw_tty.c_cc[VTIME] = '\0';    /* minimum time to wait for input */
  1152.  
  1153. #endif
  1154.       (void) ioctl(TTYIN, TCSETAW, &_raw_tty);
  1155.  
  1156.       _inraw = 1;
  1157.     }
  1158. }
  1159.  
  1160. int
  1161. ReadCh()
  1162. {
  1163.     /** read a character with Raw mode set! **/
  1164.  
  1165.     register int result;
  1166.     char ch;
  1167.     result = read(0, &ch, 1);
  1168.         return((result <= 0 ) ? EOF : ch & 0x7F);
  1169. }
  1170.  
  1171.  
  1172. outchar(c)
  1173. char c;
  1174. {
  1175.     /** output the given character.  From tputs... **/
  1176.     /** Note: this CANNOT be a macro!              **/
  1177.  
  1178.     putc(c, stdout);
  1179. }
  1180.  
  1181. @EOF
  1182.  
  1183. chmod 644 curses.c
  1184.  
  1185. echo x - group.c
  1186. cat >group.c <<'@EOF'
  1187.  
  1188.  
  1189. #include    <stdio.h>
  1190. #include    <signal.h>
  1191. #include    "tass.h"
  1192.  
  1193.  
  1194. int index_point;
  1195. int first_subj_on_screen;
  1196. int last_subj_on_screen;
  1197. char subject_search_string[LEN+1];
  1198. char author_search_string[LEN+1];
  1199. extern int cur_groupnum;
  1200. extern int last_resp;        /* page.c */
  1201. extern int this_resp;        /* page.c */
  1202. extern int space_mode;        /* select.c */
  1203. extern char *cvers;
  1204.  
  1205. char *glob_group;
  1206.  
  1207.  
  1208. #ifdef SIGTSTP
  1209. void
  1210. group_susp(i)
  1211. int i;
  1212. {
  1213.  
  1214.     Raw(FALSE);
  1215.     putchar('\n');
  1216.     signal(SIGTSTP, SIG_DFL);
  1217. #ifdef BSD
  1218.         sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
  1219. #endif
  1220.     kill(0, SIGTSTP);
  1221.  
  1222.     signal(SIGTSTP, group_susp);
  1223.     Raw(TRUE);
  1224.     mail_setup();
  1225.     show_group_page(glob_group);
  1226. }
  1227. #endif
  1228.  
  1229.  
  1230. group_page(group)
  1231. char *group;
  1232. {
  1233.     char ch;
  1234.     int i, n;
  1235.     char group_path[200];
  1236.     char *p;
  1237.     char buf[200];
  1238.     int flag;
  1239.     int sav_groupnum;
  1240.  
  1241.     glob_group = group;
  1242.     sav_groupnum = cur_groupnum;
  1243.  
  1244.     strcpy(group_path, group);        /* turn comp.unix.amiga into */
  1245.     for (p = group_path; *p; p++)        /* comp/unix/amiga */
  1246.         if (*p == '.')
  1247.             *p = '/';
  1248.  
  1249.     last_resp = -1;
  1250.     this_resp = -1;
  1251.     index_group(group, group_path);        /* update index file */
  1252.     read_newsrc_line(group);        /* get sequencer information */
  1253.  
  1254.     if (space_mode) {
  1255.         for (i = 0; i < top_base; i++)
  1256.             if (new_responses(i))
  1257.                 break;
  1258.         if (i < top_base)
  1259.             index_point = i;
  1260.         else
  1261.             index_point = top_base - 1;
  1262.     } else
  1263.         index_point = top_base - 1;
  1264.  
  1265.     show_group_page(group);
  1266.  
  1267.     while (1) {
  1268.         ch = ReadCh();
  1269.  
  1270.         if (ch > '0' && ch <= '9') {    /* 0 goes to basenote */
  1271.             prompt_subject_num(ch, group);
  1272.         } else switch (ch) {
  1273.             case 'a':    /* author search forward */
  1274.             case 'A':    /* author search backward */
  1275.                 if (index_point < 0) {
  1276.                     info_message("No articles");
  1277.                     break;
  1278.                 }
  1279.  
  1280.                 i = (ch == 'a');
  1281.  
  1282.                 n = search_author((int) base[index_point],
  1283.                                 i, group);
  1284.                 if (n < 0)
  1285.                     break;
  1286.  
  1287.                 index_point = show_page(n, group, group_path);
  1288.                 if (index_point < 0) {
  1289.                     space_mode = FALSE;
  1290.                     goto group_done;
  1291.                 }
  1292.                 show_group_page(group);
  1293.                 break;
  1294.  
  1295.             case 'I':    /* toggle inverse video */
  1296.                 inverse_okay = !inverse_okay;
  1297.                 if (inverse_okay)
  1298.                     info_message("Inverse video enabled");
  1299.                 else
  1300.                     info_message("Inverse video disabled");
  1301.                 break;
  1302.  
  1303.             case 's':    /* subscribe to this group */
  1304.                 subscribe(group, ':', my_group[cur_groupnum],
  1305.                                     TRUE);
  1306.                 sprintf(buf, "subscribed to %s", group);
  1307.                 info_message(buf);
  1308.                 break;
  1309.  
  1310.             case 'u':    /* unsubscribe to this group */
  1311.                 subscribe(group, '!', my_group[cur_groupnum],
  1312.                                     TRUE);
  1313.                 sprintf(buf, "unsubscribed to %s", group);
  1314.                 info_message(buf);
  1315.                 break;
  1316.  
  1317.             case 'g':    /* choose a new group by name */
  1318.                 n = choose_new_group();
  1319.                 if (n >= 0 && n != cur_groupnum) {
  1320.                     cur_groupnum = n;
  1321.                     index_point = -3;
  1322.                     goto group_done;
  1323.                 }
  1324.                 break;
  1325.  
  1326.             case 'c':    /* catchup--mark all articles as read */
  1327.                 if (prompt_yn("Mark everything as read? (y/n): ")) {
  1328.                 for (n = 0; n < top; n++)
  1329.                     arts[n].unread = 0;
  1330.                  for (n = INDEX_TOP ;
  1331.                     n < NOTESLINES + INDEX_TOP; n++ ) {
  1332.                      MoveCursor(n, COLS - 2);
  1333.                     putchar(' ');
  1334.                  }
  1335.                 fflush(stdout);
  1336.  /*                show_group_page(group); */
  1337.                 info_message("All articles marked as read");
  1338.                 }
  1339.                 break;
  1340.  
  1341.             case 27:    /* common arrow keys */
  1342.                 ch = ReadCh();
  1343.                 if (ch == '[' || ch == 'O')
  1344.                     ch = ReadCh();
  1345.                 switch (ch) {
  1346.                 case 'A':
  1347.                 case 'D':
  1348.                 case 'i':
  1349.                     goto group_up;
  1350.  
  1351.                 case 'B':
  1352.                 case 'I':
  1353.                 case 'C':
  1354.                     goto group_down;
  1355.                 }
  1356.                 break;
  1357.  
  1358.             case 'n':    /* next group */
  1359.                 clear_message();
  1360.                 if (cur_groupnum + 1 >= local_top)
  1361.                     info_message("No more groups");
  1362.                 else {
  1363.                     cur_groupnum++;
  1364.                     index_point = -3;
  1365.                     space_mode = FALSE;
  1366.                     goto group_done;
  1367.                 }
  1368.                 break;
  1369.  
  1370.             case 'p':    /* previous group */
  1371.                 clear_message();
  1372.                 if (cur_groupnum <= 0)
  1373.                     info_message("No previous group");
  1374.                 else {
  1375.                     cur_groupnum--;
  1376.                     index_point = -3;
  1377.                     space_mode = FALSE;
  1378.                     goto group_done;
  1379.                 }
  1380.                 break;
  1381.  
  1382.             case '\t':
  1383.                 space_mode = TRUE;
  1384.  
  1385.                 if (index_point < 0
  1386.                 || (n=next_unread((int) base[index_point]))<0) {
  1387.                     for (i = cur_groupnum+1;
  1388.                             i < local_top; i++)
  1389.                         if (unread[i] > 0)
  1390.                             break;
  1391.                     if (i >= local_top)
  1392.                         goto group_done;
  1393.  
  1394.                     cur_groupnum = i;
  1395.                     index_point = -3;
  1396.                     goto group_done;
  1397.                 }
  1398.                 index_point = show_page(n, group, group_path);
  1399.                 if (index_point < 0)
  1400.                     goto group_done;
  1401.                 show_group_page(group);
  1402.                 break;
  1403.  
  1404.             case 'K':    /* mark rest of thread as read */
  1405.                 if (new_responses(index_point)) {
  1406.                     for (i = base[index_point]; i >= 0;
  1407.                             i = arts[i].thread)
  1408.                     arts[i].unread = 0;
  1409.                     MoveCursor(INDEX_TOP +
  1410.                       (index_point - first_subj_on_screen), 78);
  1411.                     putchar(' ');
  1412.                     fflush(stdout);
  1413.                     flag = FALSE;
  1414.                 } else
  1415.                     flag = TRUE;
  1416.  
  1417.                 n = next_unread(
  1418.                     next_response(base[index_point]));
  1419.                 if (n < 0) {
  1420.                     if (flag)
  1421.                     info_message("No next unread article");
  1422.                     else
  1423.                     MoveCursor(LINES, 0);
  1424.                     break;
  1425.                 }
  1426.  
  1427.                 n = which_base(n);
  1428.                 if (n < 0) {
  1429.                     info_message(
  1430.                         "Internal error: K which_base < 0");
  1431.                     break;
  1432.                 }
  1433.  
  1434.                 if (n >= last_subj_on_screen) {
  1435.                     index_point = n;
  1436.                     show_group_page(group);
  1437.                 } else {
  1438.                     erase_subject_arrow();
  1439.                     index_point = n;
  1440.                     draw_subject_arrow();
  1441.                 }
  1442.                 break;
  1443.  
  1444.             case 'N':    /* go to next unread article */
  1445.                 if (index_point < 0) {
  1446.                     info_message("No next unread article");
  1447.                     break;
  1448.                 }
  1449.  
  1450.                 n = next_unread( (int) base[index_point]);
  1451.                 if (n == -1)
  1452.                     info_message("No next unread article");
  1453.                 else {
  1454.                     index_point =
  1455.                         show_page(n, group, group_path);
  1456.                     if (index_point < 0) {
  1457.                         space_mode = FALSE;
  1458.                         goto group_done;
  1459.                     }
  1460.                     show_group_page(group);
  1461.                 }
  1462.                 break;
  1463.  
  1464.             case 'P':    /* go to previous unread article */
  1465.                 if (index_point < 0) {
  1466.                     info_message("No previous unread article");
  1467.                     break;
  1468.                 }
  1469.  
  1470.                 n = prev_response( (int) base[index_point]);
  1471.                 n = prev_unread(n);
  1472.                 if (n == -1)
  1473.                     info_message("No previous unread article");
  1474.                 else {
  1475.                     index_point =
  1476.                         show_page(n, group, group_path);
  1477.                     if (index_point < 0) {
  1478.                         space_mode = FALSE;
  1479.                         goto group_done;
  1480.                     }
  1481.                     show_group_page(group);
  1482.                 }
  1483.                 break;
  1484.  
  1485.             case 'w':    /* post a basenote */
  1486.                 post_base(group);
  1487.                 update_newsrc(group, my_group[cur_groupnum]);
  1488.                 index_group(group, group_path);
  1489.                 read_newsrc_line(group);
  1490.                 index_point = top_base - 1;
  1491.                 show_group_page(group);
  1492.                 break;
  1493.  
  1494.             case 't':    /* return to group selection page */
  1495.                 goto group_done;
  1496.  
  1497.             case ' ':
  1498.             case '\r':
  1499.             case '\n':    /* read current basenote */
  1500.                 if (index_point < 0) {
  1501.                     info_message("*** No Articles ***");
  1502.                     break;
  1503.                 }
  1504.                 index_point = show_page((int) base[index_point],
  1505.                             group, group_path);
  1506.                 if (index_point < 0) {
  1507.                     space_mode = FALSE;
  1508.                     goto group_done;
  1509.                 }
  1510.                 show_group_page(group);
  1511.                 break;
  1512.  
  1513.             case ctrl('D'):        /* page down */
  1514.                 if (!top_base || index_point == top_base - 1)
  1515.                     break;
  1516.  
  1517.                 erase_subject_arrow();
  1518.                 index_point += NOTESLINES / 2;
  1519.                 if (index_point >= top_base)
  1520.                     index_point = top_base - 1;
  1521.  
  1522.                 if (index_point < first_subj_on_screen
  1523.                 || index_point >= last_subj_on_screen)
  1524.                     show_group_page(group);
  1525.                 else
  1526.                     draw_subject_arrow();
  1527.                 break;
  1528.  
  1529.             case '-':    /* go to last viewed article */
  1530.                 if (this_resp < 0) {
  1531.                     info_message("No last message");
  1532.                     break;
  1533.                 }
  1534.                 index_point = show_page(this_resp,
  1535.                             group, group_path);
  1536.                 if (index_point < 0) {
  1537.                     space_mode = FALSE;
  1538.                     goto group_done;
  1539.                 }
  1540.                 show_group_page(group);
  1541.                 break;
  1542.  
  1543.             case ctrl('U'):        /* page up */
  1544.                 if (!top_base)
  1545.                     break;
  1546.  
  1547.                 erase_subject_arrow();
  1548.                 index_point -= NOTESLINES / 2;
  1549.                 if (index_point < 0)
  1550.                     index_point = 0;
  1551.                 if (index_point < first_subj_on_screen
  1552.                 || index_point >= last_subj_on_screen)
  1553.                     show_group_page(group);
  1554.                 else
  1555.                     draw_subject_arrow();
  1556.                 break;
  1557.  
  1558.             case 'v':
  1559.                 info_message(cvers);
  1560.                 break;
  1561.  
  1562.             case '!':
  1563.                 shell_escape();
  1564.                 show_group_page(group);
  1565.                 break;
  1566.  
  1567.             case ctrl('N'):
  1568.             case 'j':        /* line down */
  1569. group_down:
  1570.                 if (!top_base || index_point + 1 >= top_base)
  1571.                     break;
  1572.  
  1573.                 if (index_point + 1 >= last_subj_on_screen) {
  1574.                     index_point++;
  1575.                     show_group_page(group);
  1576.                 } else {
  1577.                     erase_subject_arrow();
  1578.                     index_point++;
  1579.                     draw_subject_arrow();
  1580.                 }
  1581.                 break;
  1582.  
  1583.             case ctrl('P'):
  1584.             case 'k':        /* line up */
  1585. group_up:
  1586.                 if (!top_base || !index_point)
  1587.                     break;
  1588.  
  1589.                 if (index_point <= first_subj_on_screen) {
  1590.                     index_point--;
  1591.                     show_group_page(group);
  1592.                 } else {
  1593.                     erase_subject_arrow();
  1594.                     index_point--;
  1595.                     draw_subject_arrow();
  1596.                 }
  1597.                 break;
  1598.  
  1599.             case ctrl('R'):
  1600.             case ctrl('L'):
  1601.             case ctrl('W'):
  1602.             case 'i':        /* return to index */
  1603.                     show_group_page(group);
  1604.                     break;
  1605.  
  1606.             case '/':        /* forward search */
  1607.                     search_subject(TRUE, group);
  1608.                     break;
  1609.  
  1610.             case '?':        /* backward search */
  1611.                     search_subject(FALSE, group);
  1612.                     break;
  1613.  
  1614.             case 'q':        /* quit */
  1615.                     index_point = -2;
  1616.                     space_mode = FALSE;
  1617.                     goto group_done;
  1618.  
  1619.             case 'h':
  1620.                 tass_group_help();
  1621.                 show_group_page(group);
  1622.                 break;
  1623.  
  1624.             default:
  1625.                 info_message("Bad command.  Type 'h' for help.");
  1626.         }
  1627.     }
  1628.  
  1629. group_done:
  1630.     fix_new_highest(sav_groupnum);
  1631.     update_newsrc(group, my_group[sav_groupnum]);
  1632.  
  1633.     if (index_point == -2)
  1634.         tass_done(0);
  1635. }
  1636.  
  1637.  
  1638. /*
  1639.  *  Correct highest[] for the group selection page display since
  1640.  *  new articles may have been read or marked unread
  1641.  */
  1642.  
  1643. fix_new_highest(groupnum)
  1644. int groupnum;
  1645. {
  1646.     int i;
  1647.     int sum = 0;
  1648.  
  1649.     for (i = 0; i < top; i++)
  1650.         if (arts[i].unread)
  1651.             sum++;
  1652.  
  1653.     unread[groupnum] = sum;
  1654. }
  1655.  
  1656.  
  1657. show_group_page(group)
  1658. char *group;
  1659. {
  1660.     int i;
  1661.     int n;
  1662.     char resps[10];
  1663.     char new_resps;
  1664.     int respnum;
  1665.  
  1666. #ifdef SIGTSTP
  1667.     signal(SIGTSTP, group_susp);
  1668. #endif
  1669.  
  1670.     ClearScreen();
  1671.     printf("%s\r\n", nice_time());    /* time in upper left */
  1672.     center_line(1, group);
  1673.  
  1674.     if (mail_check()) {            /* you have mail message in */
  1675.         MoveCursor(0, 66);        /* upper right */
  1676.         printf("you have mail\n");
  1677.     }
  1678.  
  1679.     MoveCursor(INDEX_TOP, 0);
  1680.  
  1681.     first_subj_on_screen = (index_point / NOTESLINES) * NOTESLINES;
  1682.     if (first_subj_on_screen < 0)
  1683.         first_subj_on_screen = 0;
  1684.  
  1685.     last_subj_on_screen = first_subj_on_screen + NOTESLINES;
  1686.     if (last_subj_on_screen >= top_base) {
  1687.         last_subj_on_screen = top_base;
  1688.         first_subj_on_screen = top_base - NOTESLINES;
  1689.  
  1690.         if (first_subj_on_screen < 0)
  1691.             first_subj_on_screen = 0;
  1692.     }
  1693.  
  1694.     for (i = first_subj_on_screen; i < last_subj_on_screen; i++) {
  1695.         if (new_responses(i))
  1696.             new_resps = '+';
  1697.         else
  1698.             new_resps = ' ';
  1699.  
  1700.         n = nresp(i);
  1701.         if (n)
  1702.             sprintf(resps, "%4d", n);
  1703.         else
  1704.             strcpy(resps, "    ");
  1705.  
  1706.         respnum = base[i];
  1707.  
  1708.         printf("  %4d  %-*s %s %-*s %c\r\n",
  1709.                 i + 1,
  1710.                 MAX_SUBJ,
  1711.                 arts[respnum].subject,
  1712.                 resps,
  1713.                 MAX_FROM,
  1714.                 arts[respnum].from,
  1715.                 new_resps);
  1716.     }
  1717.  
  1718.     if (top_base <= 0)
  1719.         info_message("*** No Articles ***");
  1720.     else if (last_subj_on_screen == top_base)
  1721.         info_message("*** End of Articles ***");
  1722.  
  1723.     if (top_base > 0)
  1724.         draw_subject_arrow();
  1725. }
  1726.  
  1727. draw_subject_arrow() {
  1728.  
  1729.     draw_arrow(INDEX_TOP + (index_point-first_subj_on_screen) );
  1730. }
  1731.  
  1732. erase_subject_arrow() {
  1733.  
  1734.     erase_arrow(INDEX_TOP + (index_point-first_subj_on_screen) );
  1735. }
  1736.  
  1737.  
  1738. prompt_subject_num(ch, group)
  1739. char ch;
  1740. char *group;
  1741. {
  1742. int num;
  1743.  
  1744.  
  1745.     clear_message();
  1746.  
  1747.     if ((num = parse_num(ch, "Read article> ")) == -1) {
  1748.         clear_message();
  1749.         return FALSE;
  1750.     }
  1751.     num--;        /* index from 0 (internal) vs. 1 (user) */
  1752.  
  1753.     if (num >= top_base)
  1754.         num = top_base - 1;
  1755.  
  1756.     if (num >= first_subj_on_screen
  1757.     &&  num < last_subj_on_screen) {
  1758.         erase_subject_arrow();
  1759.         index_point = num;
  1760.         draw_subject_arrow();
  1761.     } else {
  1762.         index_point = num;
  1763.         show_group_page(group);
  1764.     }
  1765. }
  1766.  
  1767.  
  1768. search_author(current_art, forward, group)
  1769. int current_art;
  1770. int forward;
  1771. char *group;
  1772. {
  1773.     char buf[LEN+1];
  1774.     char buf2[LEN+1];
  1775.     int i;
  1776.     int len;
  1777.     char *prompt;
  1778.  
  1779.     clear_message();
  1780.  
  1781.     if (forward)
  1782.         prompt = "Author search forward: ";
  1783.     else
  1784.         prompt = "Author search backward: ";
  1785.  
  1786.     if (!parse_string(prompt, buf))
  1787.         return -1;
  1788.  
  1789.     if (strlen(buf))
  1790.         strcpy(author_search_string, buf);
  1791.     else if (!strlen(author_search_string)) {
  1792.         info_message("No search string");
  1793.         return -1;
  1794.     }
  1795.  
  1796.     make_lower(author_search_string, buf);
  1797.     len = strlen(buf);
  1798.  
  1799.     i = current_art;
  1800.  
  1801.     do {
  1802.         if (forward) {
  1803.             i = next_response(i);
  1804.             if (i < 0)
  1805.                 i = 0;
  1806.         } else {
  1807.             i = prev_response(i);
  1808.             if (i < 0)
  1809.                 i = top - 1;
  1810.         }
  1811.  
  1812.         make_lower(arts[i].from, buf2);
  1813.         if (match(buf, buf2, len))
  1814.             return i;
  1815.     } while (i != current_art);
  1816.  
  1817.     info_message("No match");
  1818.     return -1;
  1819. }
  1820.  
  1821.  
  1822. search_subject(forward, group)
  1823. int forward;
  1824. char *group;
  1825. {
  1826.     char buf[LEN+1];
  1827.     char buf2[LEN+1];
  1828.     int i;
  1829.     int len;
  1830.     char *prompt;
  1831.  
  1832.     clear_message();
  1833.  
  1834.     if (forward)
  1835.         prompt = "/";
  1836.     else
  1837.         prompt = "?";
  1838.  
  1839.     if (!parse_string(prompt, buf))
  1840.         return;
  1841.  
  1842.     if (strlen(buf))
  1843.         strcpy(subject_search_string, buf);
  1844.     else if (!strlen(subject_search_string)) {
  1845.         info_message("No search string");
  1846.         return;
  1847.     }
  1848.  
  1849.     i = index_point;
  1850.  
  1851.     make_lower(subject_search_string, buf);
  1852.     len = strlen(buf);
  1853.  
  1854.     do {
  1855.         if (forward)
  1856.             i++;
  1857.         else
  1858.             i--;
  1859.  
  1860.         if (i >= top_base)
  1861.             i = 0;
  1862.         if (i < 0)
  1863.             i = top_base - 1;
  1864.  
  1865.         make_lower(arts[base[i]].subject, buf2);
  1866.         if (match(buf, buf2, len)) {
  1867.             if (i >= first_subj_on_screen
  1868.             &&  i < last_subj_on_screen) {
  1869.                 erase_subject_arrow();
  1870.                 index_point = i;
  1871.                 draw_subject_arrow();
  1872.             } else {
  1873.                 index_point = i;
  1874.                 show_group_page(group);
  1875.             }
  1876.             return;
  1877.         }
  1878.     } while (i != index_point);
  1879.  
  1880.     info_message("No match");
  1881. }
  1882.  
  1883.  
  1884. /*
  1885.  *  Post an original article (not a followup)
  1886.  */
  1887.  
  1888. post_base(group)
  1889. char *group;
  1890. {
  1891.     FILE *fp;
  1892.     char nam[100];
  1893.     char ch;
  1894.     char subj[LEN+1];
  1895.     char buf[200];
  1896.  
  1897.     if (!parse_string("Subject: ", subj))
  1898.         return;
  1899.     if (subj[0] == '\0')
  1900.         return;
  1901.  
  1902.     setuid(real_uid);
  1903.     setgid(real_gid);
  1904.  
  1905.     sprintf(nam, "%s/.article", homedir);
  1906.     if ((fp = fopen(nam, "w")) == NULL) {
  1907.         fprintf(stderr, "can't open %s: ", nam);
  1908.         perror("");
  1909.         setuid(tass_uid);
  1910.         setgid(tass_gid);
  1911.         return(FALSE);
  1912.     }
  1913.     chmod(nam, 0600);
  1914.  
  1915.     fprintf(fp, "Subject: %s\n", subj);
  1916.     fprintf(fp, "Newsgroups: %s\n", group);
  1917.     fprintf(fp, "Distribution: \n");
  1918.     if (*my_org)
  1919.         fprintf(fp, "Organization: %s\n", my_org);
  1920.     fprintf(fp, "\n");
  1921.  
  1922.     add_signature(fp, FALSE);
  1923.     fclose(fp);
  1924.  
  1925.     ch = 'e';
  1926.     while (1) {
  1927.         switch (ch) {
  1928.         case 'e':
  1929.             invoke_editor(nam);
  1930.             break;
  1931.  
  1932.         case 'a':
  1933.             setuid(tass_uid);
  1934.             setgid(tass_gid);
  1935.             return FALSE;
  1936.  
  1937.         case 'p':
  1938.             printf("\nPosting...  ");
  1939.             fflush(stdout);
  1940.             sprintf(buf, "%s/inews -h < %s", LIBDIR, nam);
  1941.             if (invoke_cmd(buf)) {
  1942.                 printf("article posted\n");
  1943.                 fflush(stdout);
  1944.                 goto post_base_done;
  1945.             } else {
  1946.                 printf("article rejected\n");
  1947.                 fflush(stdout);
  1948.                 break;
  1949.             }
  1950.         }
  1951.  
  1952.         do {
  1953.             MoveCursor(LINES, 0);
  1954.             fputs("abort, edit, post: ", stdout);
  1955.             fflush(stdout);
  1956.             ch = ReadCh();
  1957.         } while (ch != 'a' && ch != 'e' && ch != 'p');
  1958.     }
  1959.  
  1960. post_base_done:
  1961.     setuid(tass_uid);
  1962.     setgid(tass_gid);
  1963.  
  1964.     continue_prompt();
  1965.  
  1966.     return(TRUE);
  1967. }
  1968.  
  1969.  
  1970. /*
  1971.  *  Return the number of unread articles there are within a thread
  1972.  */
  1973.  
  1974. new_responses(thread)
  1975. int thread;
  1976. {
  1977.     int i;
  1978.     int sum = 0;
  1979.  
  1980.     for (i = base[thread]; i >= 0; i = arts[i].thread)
  1981.         if (arts[i].unread)
  1982.             sum++;
  1983.  
  1984.     return sum;
  1985. }
  1986.  
  1987.  
  1988. tass_group_help() {
  1989.     char title[100];
  1990.  
  1991.     sprintf(title, "%s, Index Page Commands", TASS_HEADER);
  1992.     ClearScreen();
  1993.     center_line(0, title);
  1994.  
  1995.     MoveCursor(2, 0);
  1996.  
  1997.     printf("\t4\tSelect article 4\r\n");
  1998.     printf("\t<CR>\tRead current article\r\n");
  1999.     printf("\t<TAB>\tView next unread article or group\r\n");
  2000.     printf("\t^D^U\tPage down (^U=page up)\r\n");
  2001.     printf("\taA\tAuthor search forward (A=backward)\r\n");
  2002.     printf("\tc\tMark all articles as read\r\n");
  2003.     printf("\tg\tChoose a new group by name\r\n");
  2004.     printf("\tjk\tDown (k=up) a line\r\n");
  2005.     printf("\tK\tMark thread as read & advance\r\n");
  2006.     printf("\tnp\tGo to next (p=previous) group\r\n");
  2007.     printf("\tNP\tGo to next (P=previous) unread article\r\n");
  2008.     printf("\tq\tQuit\r\n");
  2009.     printf("\tsu\tSubscribe (u=unsubscribe) to this group\r\n");
  2010.     printf("\tt\tReturn to group selection index\r\n");
  2011.     printf("\tw\tPost an article\r\n");
  2012.     printf("\t/?\tSearch forward (?=backward) for subject\r\n");
  2013.     printf("\t-\tShow last article\r\n");
  2014.  
  2015.     center_line(LINES, "-- hit any key --");
  2016.     ReadCh();
  2017. }
  2018.  
  2019. @EOF
  2020.  
  2021. chmod 644 group.c
  2022.  
  2023. echo x - hashstr.c
  2024. cat >hashstr.c <<'@EOF'
  2025.  
  2026. #include    <stdio.h>
  2027.  
  2028.  
  2029. /*
  2030.  *  Maintain a table of all strings we have seen.
  2031.  *  If a new string comes in, add it to the table and return a pointer
  2032.  *  to it.  If we've seen it before, just return the pointer to it.
  2033.  *
  2034.  *  Usage:  hash_str("some string") returns char *
  2035.  *
  2036.  *  Spillovers are chained on the end
  2037.  */
  2038.  
  2039.  
  2040. /*
  2041.  *  Arbitrary table size, but make sure it's prime!
  2042.  */
  2043.  
  2044. /* #define        TABLE_SIZE    1409    */
  2045.  
  2046. #define        TABLE_SIZE    2411
  2047.  
  2048.  
  2049.  
  2050. struct hashnode {
  2051.     char *s;            /* the string we're saving */
  2052.     struct hashnode *next;        /* chain for spillover */
  2053. };
  2054.  
  2055. struct hashnode *table[ TABLE_SIZE ];
  2056.  
  2057. extern char *my_malloc();
  2058. struct hashnode *add_string();
  2059.  
  2060.  
  2061. char *
  2062. hash_str(s)
  2063. char *s;
  2064. {
  2065.     struct hashnode *p;    /* used to descend the spillover structs */
  2066.     long h;            /* result of hash:  index into hash table */
  2067.  
  2068.     if (s == NULL)
  2069.         return(NULL);
  2070.  
  2071.     {
  2072.         char *t = s;
  2073.  
  2074.         h = *t++;
  2075.         while (*t)
  2076.             h = ((h << 1) ^ *t++) % TABLE_SIZE;
  2077.     /*        h = (h * 128 + *t++) % TABLE_SIZE;    */
  2078.     }
  2079.  
  2080.     p = table[h];
  2081.  
  2082.     if (p == NULL) {
  2083.         table[h] = add_string(s);
  2084.         return table[h]->s;
  2085.     }
  2086.  
  2087.     while (1) {
  2088.         if (strcmp(s, p->s) == 0)
  2089.             return(p->s);
  2090.  
  2091.         if (p->next == NULL) {
  2092.             p->next = add_string(s);
  2093.             return p->next->s;
  2094.         } else
  2095.             p = p->next;
  2096.     }
  2097. }
  2098.  
  2099.  
  2100. struct hashnode *
  2101. add_string(s)
  2102. char *s;
  2103. {
  2104.     struct hashnode *p;
  2105.     extern char *strcpy();
  2106.     int *iptr;
  2107.  
  2108.     p = (struct hashnode *) my_malloc(sizeof(*p));
  2109.  
  2110.     p->next = NULL;
  2111.     iptr = (int *) my_malloc(strlen(s) + sizeof(int) + 1);
  2112.     *iptr++ = -1;
  2113.     p->s = (char *) iptr;
  2114.     strcpy(p->s, s);
  2115.     return(p);
  2116. }
  2117.  
  2118.  
  2119. hash_init() {
  2120.     int i;
  2121.  
  2122.     for (i = 0; i < TABLE_SIZE; i++)
  2123.         table[i] = NULL;
  2124. }
  2125.  
  2126.  
  2127. hash_reclaim() {
  2128.     int i;
  2129.     struct hashnode *p, *next;
  2130.     int *iptr;
  2131.  
  2132.     for (i = 0; i < TABLE_SIZE; i++)
  2133.         if (table[i] != NULL) {
  2134.             p = table[i];
  2135.             while (p != NULL) {
  2136.                 next = p->next;
  2137.                 iptr = (int *) p->s;
  2138.                 free(--iptr);
  2139.                 free(p);
  2140.                 p = next;
  2141.             }
  2142.             table[i] = NULL;
  2143.         }
  2144. }
  2145.  
  2146. @EOF
  2147.  
  2148. chmod 600 hashstr.c
  2149.  
  2150. exit 0
  2151.