home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / vsoup128.zip / newsrc.cc < prev    next >
C/C++ Source or Header  |  1997-02-06  |  17KB  |  767 lines

  1. //  $Id: newsrc.cc 1.22 1997/02/06 09:55:30 hardy Exp $
  2. //
  3. //  This progam/module was written by Hardy Griech based on ideas and
  4. //  pieces of code from Chin Huang (cthuang@io.org).  Bug reports should
  5. //  be submitted to rgriech@ibm.net.
  6. //
  7. //  This file is part of soup++ for OS/2.  Soup++ including this file
  8. //  is freeware.  There is no warranty of any kind implied.  The terms
  9. //  of the GNU Gernal Public Licence are valid for this piece of software.
  10. //
  11. //  rg110796:
  12. //  - introduction of a hash list because performance was real bad
  13. //  - grpFirst/grpNext now returning only subscribed groups
  14. //  - newsrc is written if there were any changes (onyl then!)
  15. //
  16.  
  17.  
  18. #include <assert.h>
  19. #include <fcntl.h>
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <unistd.h>
  24.  
  25. #include "mts.hh"
  26. #include "newsrc.hh"
  27. #include "util.hh"
  28.  
  29.  
  30.  
  31. TNewsrc::TNewsrc( void )
  32. {
  33.     int i;
  34.     
  35. #ifdef TRACE_ALL
  36.     printfT( "TNewsrc::TNewsrc()\n" );
  37. #endif
  38.     groups         = NULL;
  39.     filename       = xstrdup("");
  40.     cacheGroup     = NULL;
  41.     cacheGroupName = NULL;
  42.     addGroupP      = NULL;
  43.     fileRead       = 0;
  44.     for (i = 0;  i < NEWSRC_HASHSIZE;  ++i)
  45.     hashTab[i] = (pGroup)NULL;
  46. }   // TNewsrc::TNewsrc
  47.  
  48.  
  49.  
  50. TNewsrc::~TNewsrc()
  51. {
  52.     Range *rp1, *rp2;
  53.     pGroup gp1, gp2;
  54.     
  55. ////    delete filename;
  56.     gp1 = groups;
  57.     while (gp1 != NULL) {
  58.     rp1 = gp1->readList;
  59.     while (rp1 != NULL) {
  60.         rp2 = rp1->next;
  61. ////        delete rp1;
  62.         rp1 = rp2;
  63.     }
  64.     gp2 = gp1->next;
  65. ////    delete gp1->name;
  66. ////    delete gp1;
  67.     gp1 = gp2;
  68.     }
  69. }   // TNewsrc::~TNewsrc
  70.  
  71.  
  72.  
  73. TNewsrc::Range *TNewsrc::getReadList( TFile &nrcFile )
  74. //
  75. //  Read the article numbers from a .newsrc line.
  76. //
  77. {
  78.     Range *pLast, *rp, *head;
  79.     int  c;
  80.     long lo,hi;
  81.     char buf[10];
  82.  
  83.     /* Initialize subscription list */
  84.     pLast = NULL;
  85.     head = NULL;
  86.  
  87.     /* Expect [ \n] */
  88.     c = nrcFile.getcc();
  89.  
  90.     while (c != '\n'  &&  c != EOF) {
  91.     /* Expect number */
  92.     if (nrcFile.scanf("%*[ \t]%ld",&lo) != 1) {
  93.         nrcFile.fgets( buf,sizeof(buf),1 );
  94.         break;
  95.     }
  96.     lo = (lo >= 0) ? lo : 0;
  97.  
  98.     /* Get space for new list entry */
  99.     rp = new Range;
  100.     rp->next = NULL;
  101.  
  102.     /* Expect [-,\n] */
  103.     c = nrcFile.getcc();
  104.     if (c == '-') {
  105.         /* Is a range */
  106.         /* Expect number */
  107.         if (nrcFile.scanf("%*[ \t]%ld",&hi) != 1) {
  108.         nrcFile.fgets( buf,sizeof(buf),1 );
  109.         break;
  110.         }
  111.         hi = (hi >= 0) ? hi : 0;
  112.  
  113.         rp->lo = lo;
  114.         rp->hi = hi;
  115.  
  116.         /* Reverse them in case they're backwards */
  117.         if (hi < lo) {
  118.         rp->lo = hi;
  119.         rp->hi = lo;
  120.         }
  121.         if (rp->lo == 0)  //???
  122.         rp->lo = 1;
  123.  
  124.         /* Expect [,\n] */
  125.         c = nrcFile.getcc();
  126.     } else {
  127.         /* Not a range */
  128.         rp->lo = rp->hi = lo;
  129.     }
  130.     if (rp->lo <= 0) {   //???
  131. ////        delete rp;
  132.         continue;
  133.     }
  134.  
  135.     /* Check if range overlaps last one */
  136.     if (pLast != NULL  &&  rp->lo <= pLast->hi + 1) {
  137.         /* Combine ranges */
  138.         if (rp->lo < pLast->lo)
  139.         pLast->lo = rp->lo;
  140.         if (rp->hi > pLast->hi)
  141.         pLast->hi = rp->hi;
  142.  
  143.         /* Free old (ehm new?) one */
  144. ////        delete rp;
  145.     } else {
  146.         /* No overlap, update pointers */
  147.         if (pLast == NULL) {
  148.         head = rp;
  149.         } else {
  150.         pLast->next = rp;
  151.         }
  152.         pLast = rp;
  153.     }
  154.     }
  155.  
  156.     return head;
  157. }   // TNewsrc::getReadList
  158.  
  159.  
  160.  
  161. int TNewsrc::readFile( const char *newsrcFile )
  162. //
  163. //  Read the .newsrc file.
  164. //  Return 0 on error (otherwise 1)
  165. //
  166. {
  167.     TFile nrcFile;
  168.     char groupName[BUFSIZ];
  169.     char buf[10];
  170.  
  171. #ifdef TRACE_ALL
  172.     printfT("TNewsrc::readFile(%s)\n",newsrcFile);
  173. #endif
  174.     assert( !fileRead );
  175.  
  176.     filename = xstrdup(newsrcFile);
  177.  
  178.     /* Open it */
  179.     if ( !nrcFile.open(newsrcFile,TFile::mread,TFile::otext)) {
  180.     hprintfT( STDERR_FILENO, "cannot open %s\n", newsrcFile );
  181.     fileRead = 1;
  182.     return 0;
  183.     }
  184.  
  185.     sema.Request();
  186.  
  187.     /* Read newsgroup entry */
  188.     for (;;) {
  189.     pGroup np;
  190.     int res;
  191.  
  192.     res = nrcFile.scanf("%[^:! \t\n]%*[ \t]", groupName);
  193.     if (res == 0) {
  194.         nrcFile.fgets( buf,sizeof(buf),1 );
  195.         continue;
  196.     }
  197.     if (res != 1)
  198.         break;
  199.  
  200. #ifdef DEBUG_ALL
  201.     printfT( "TNewsrc::readfile(): %s\n",groupName );
  202. #endif
  203.     if (strchr(groupName,',') != NULL) {
  204.         hprintfT( STDERR_FILENO,"ill groupname in %s: %s\n", newsrcFile,groupName );
  205.         nrcFile.fgets( buf,sizeof(buf),1 );
  206.         continue;
  207.     }
  208.     else if (grpExists(groupName)) {
  209.         hprintfT( STDERR_FILENO,"groupname %s found twice in\n\t%s - second occurence removed\n",
  210.               groupName,newsrcFile );
  211.         nrcFile.fgets( buf,sizeof(buf),1 );
  212.         continue;
  213.     }
  214.  
  215.     if (groupName[0] == '\0')
  216.         break;
  217.  
  218.     //
  219.     //  Allocate a new entry
  220.     //
  221.     np = (pGroup)grpAdd( groupName );
  222.         nrcFile.scanf( "%*[ \t]%1[:!]",buf );
  223.     if (*buf == '\0') {
  224.         /* The user didn't end the line with a colon. */
  225.         nrcFile.fgets( buf,sizeof(buf),1 );
  226.         np->subscribed = 1;
  227.     } else {
  228.         /* Parse subscription list */
  229.         np->subscribed = (*buf == ':');
  230.         np->readList = getReadList(nrcFile);
  231.     }
  232.     }
  233.  
  234.     nrcFile.close();
  235.     fileRead = 1;
  236.     fileChanged = 0;
  237.  
  238.     sema.Release();
  239.  
  240.     return 1;
  241. }   // TNewsrc::readFile
  242.  
  243.  
  244.  
  245. void TNewsrc::putReadList( TFile &fd, Range *head )
  246. //
  247. //  Write the article numbers for a .newsrc entry.
  248. //
  249. {
  250.     if (head == NULL)
  251.     fd.putcc('0');
  252.     else {
  253.     while (head != NULL) {
  254.         if (head->lo == head->hi)
  255.         fd.printf("%ld", head->lo);
  256.         else
  257.         fd.printf("%ld-%ld", head->lo, head->hi);
  258.         head = head->next;
  259.         if (head != NULL)
  260.         fd.putcc(',');
  261.     }
  262.     }
  263.     fd.putcc('\n');
  264. }   // TNewsrc::putReadList
  265.  
  266.  
  267.  
  268. int TNewsrc::writeFile(void)
  269. //
  270. //  Rewrite the updated .newsrc file
  271. //
  272. {
  273.     char oldFile[FILENAME_MAX];
  274.     TFile nrcFile;
  275.     pGroup np;
  276.  
  277. #ifdef TRACE_ALL
  278.     printfT( "TNewsrc::writeFile()\n" );
  279. #endif
  280.     if (filename[0] == '\0')
  281.     return 1;                        // successful (cause nothing to do)
  282.     if (groups == NULL  ||  !fileRead  ||  !fileChanged)
  283.     return 1;
  284.  
  285.     sema.Request();
  286.  
  287.     //
  288.     //  Back up old .newsrc file.
  289.     //
  290.     sprintfT(oldFile, "%s.old", filename);
  291.     removeT(oldFile);
  292.     renameT(filename, oldFile);
  293.  
  294.     if ( !nrcFile.open(filename,TFile::mwrite,TFile::otext,1)) {
  295.     hprintfT( STDERR_FILENO, "cannot write %s\n", filename );
  296.     sema.Release();
  297.     return 0;
  298.     }
  299.  
  300.     for (np = groups; np != NULL; np = np->next) {
  301.     nrcFile.printf( "%s%c ", np->name, np->subscribed ? ':' : '!' );
  302.     putReadList( nrcFile, np->readList );
  303.     }
  304.     nrcFile.close();
  305.     sema.Release();
  306.     return 1;
  307. }   // TNewsrc::writeFile
  308.  
  309.  
  310.  
  311. //--------------------------------------------------------------------------------
  312.  
  313.  
  314.  
  315. TNewsrc::pGroup TNewsrc::getGroupP( const char *groupName )
  316. //
  317. //  check, if groupName exists (return NULL, if not, otherwise pGroup)
  318. //  "cache" is updated
  319. //
  320. {
  321.     pGroup np;
  322.     pGroup res;
  323.  
  324. #ifdef TRACE_ALL
  325. //    printfT( "TNewsrc::getGroupP(%s)\n", groupName );
  326. #endif
  327.  
  328.     sema.Request();
  329.     if (cacheGroupName == NULL  ||  stricmp(groupName,cacheGroupName) != 0) {
  330.     int hasho;
  331.  
  332.     hasho = hashi(groupName,NEWSRC_HASHSIZE);
  333.     
  334.     for (np = hashTab[hasho];  np != NULL;  np = np->hashNext) {
  335.         if (stricmp(np->name, groupName) == 0) {
  336.         cacheGroupName = np->name;
  337.         cacheGroup     = np;
  338.         break;
  339.         }
  340.     }
  341.     if (np == NULL) {
  342.         cacheGroupName = NULL;
  343.         cacheGroup     = NULL;
  344.     }
  345.     }
  346.     res = cacheGroup;
  347.     sema.Release();
  348. #ifdef TRACE_ALL
  349. //    printfT( "TNewsrc::getGroupP(%s) = %p\n", groupName,cacheGroup );
  350. #endif
  351.     return res;
  352. }   // TNewsrc::getGroupP
  353.  
  354.  
  355.  
  356. void TNewsrc::grpFixReadList( const char *groupName, long groupLo, long groupHi )
  357. //
  358. //  Sanity fixes to the read article number list
  359. //
  360. {
  361.     pGroup np = getGroupP( groupName );
  362.     Range *rp1, *rp2;
  363.  
  364. #ifdef TRACE_ALL
  365.     printfT( "TNewsrc::grpFixReadList(%s,%ld,%ld)\n",groupName,groupLo,groupHi );
  366. #endif
  367.     assert( np != NULL );
  368.     sema.Request();
  369.  
  370.     //
  371.     //  If the highest read article is greater than the highest
  372.     //  available article, assume the group has been reset.
  373.     //  But:  allow a small threshold due to cancelled articles (4)
  374.     //
  375.     if (np->readList != NULL) {
  376.     for (rp1 = np->readList; rp1->next != NULL; rp1 = rp1->next)    // find end of list
  377.         ;
  378.     if (rp1->hi > groupHi + ((groupHi > 100) ? 4 : 0)) {
  379.         //
  380.         //  delete all of the list
  381.         //
  382.         rp1 = np->readList;
  383.         while (rp1 != NULL) {
  384.         rp2 = rp1->next;
  385. ////        delete rp1;
  386.         rp1 = rp2;
  387.         }
  388.         np->readList = NULL;
  389.     }
  390.     }
  391.  
  392.     //
  393.     //  eliminate ranges lower than the lowest available article
  394.     //  proceed from the beginning of the list...
  395.     //
  396.     rp1 = np->readList;
  397.     while (rp1 != NULL  &&  groupLo > rp1->hi) {
  398. #ifdef DEBUG_ALL
  399.     printfT( "ellower: \n" );
  400. #endif
  401.     np->readList = rp1->next;
  402. ////    delete rp1;
  403.     rp1 = np->readList;
  404.     }
  405.  
  406.     //
  407.     //  All entries with a range below groupLo have been eliminated.  Also no entry of
  408.     //  the list has a higher number than groupHi.  This means, that all entries are
  409.     //  in the range of groupLo..groupHi (or the list is empty).
  410.     //  If the list is empty, an entry from 1..groupLo-1 must be generated.  If the
  411.     //  list is not empty and groupLo is smaller than rp1->lo again 1..groupLo-1 must
  412.     //  be generated (if groupLo==1, nothing will be done)
  413.     //
  414.     rp1 = np->readList;
  415.     if (rp1 == NULL  ||  groupLo < rp1->lo) {
  416.     if (groupLo > 1) {
  417.         rp2 = new Range;
  418.         rp2->next = rp1;
  419.         rp2->lo = 1;
  420.         rp2->hi = groupLo-1;
  421.         np->readList = rp2;
  422.     }
  423.     }
  424.     else if (rp1 != NULL)
  425.     rp1->lo = 1;                 // old entry can be used!
  426.  
  427. #ifdef TRACE_ALL
  428.     printfT( "grpFixReadList(): ende\n" );
  429. #endif
  430.     sema.Release();
  431.  
  432.     //
  433.     //  fileChanged is not set intentionally
  434.     //
  435.     return;
  436. }   // TNewsrc::grpFixReadList
  437.  
  438.  
  439.  
  440. const char *TNewsrc::grpFirst( void )
  441. //
  442. //  return first subscribed group (NULL, if none)
  443. //
  444. {
  445.     pGroup pp;
  446.  
  447. #ifdef TRACE_ALL
  448.     printfT( "TNewsrc::grpFirst()\n" );
  449. #endif
  450.     for (pp = groups;  pp != NULL;  pp = pp->next) {
  451. #ifdef TRACE_ALL
  452.     printfT( "TNewsrc::grpFirst(): %s,%d\n",pp->name,pp->subscribed );
  453. #endif
  454.     if (pp->subscribed)
  455.         return pp->name;
  456.     }
  457.     return NULL;
  458. }   // TNewsrc::grpFirst
  459.  
  460.  
  461.  
  462. const char *TNewsrc::grpNext( const char *prevGroupName )
  463. //
  464. //
  465. //  return next subscribed group (NULL, if none)
  466. //
  467. {
  468.     pGroup np = getGroupP( prevGroupName );
  469.  
  470. #ifdef TRACE_ALL
  471.     printfT( "TNewsrc::grpNext(%s)\n",prevGroupName );
  472. #endif
  473.     assert( prevGroupName[0] != '\0' );
  474.     assert( np != NULL );
  475.  
  476.     while (np != NULL) {
  477.     np = np->next;
  478.     if (np != NULL  &&  np->subscribed)
  479.         return np->name;
  480.     }
  481.     return NULL;
  482. }   // TNewsrc::grpNext
  483.  
  484.  
  485.  
  486. int  TNewsrc::grpSubscribed( const char *groupName )
  487. {
  488.     pGroup np = getGroupP( groupName );
  489.  
  490. #ifdef TRACE_ALL
  491.     printfT( "TNewsrc::grpSubscribed(%s)\n",groupName );
  492. #endif
  493.     assert( np != NULL );
  494. #ifdef TRACE_ALL
  495.     printfT( "TNewsrc::grpSubscribed(%s): %d\n",np->name,np->subscribed );
  496. #endif
  497.     return np->subscribed;
  498. }   // TNewsrc::grpSubscribed
  499.  
  500.  
  501.  
  502. int TNewsrc::grpExists( const char *groupName )
  503. {
  504.     pGroup np = getGroupP( groupName );
  505.  
  506. #ifdef TRACE_ALL
  507.     printfT( "TNewsrc::grpExists(%s)\n", groupName );
  508. #endif
  509.  
  510.     return np != NULL;
  511. }   // TNewsrc::grpExists
  512.  
  513.  
  514.  
  515. void TNewsrc::grpUnsubscribe( const char *groupName )
  516. {
  517.     pGroup np = getGroupP( groupName );
  518.  
  519. #ifdef TRACE_ALL
  520.     printfT( "TNewsrc::grpUnsubscibe(%s)\n",groupName );
  521. #endif
  522.     if (np != NULL) {
  523.     np->subscribed = 0;
  524.     fileChanged = 1;
  525.     }
  526. }   // TNewsrc::grpUnsubscribe
  527.  
  528.  
  529.  
  530. long TNewsrc::grpFirstUnread( const char *groupName, long groupLo )
  531. //
  532. //  Get first unread article number.
  533. //
  534. {
  535.     pGroup np = getGroupP( groupName );
  536.     long res;
  537.     
  538. #ifdef TRACE_ALL
  539.     printfT( "TNewsrc::grpFirstUnread(%s,%ld)\n",groupName,groupLo );
  540. #endif
  541.     assert( np != NULL );
  542.  
  543.     sema.Request();
  544.     if (np->readList == NULL)
  545.     res = groupLo;
  546.     else {
  547.     if (groupLo < np->readList->lo)
  548.         res = groupLo;
  549.     else
  550.         res = np->readList->hi + 1;
  551.     }
  552.     sema.Release();
  553.     return res;
  554. }   // TNewsrc::grpFirstUnread
  555.  
  556.  
  557.  
  558. int TNewsrc::artIsRead( const char *groupName, long artNum )
  559. //
  560. //  Determine if the article number has been read
  561. //
  562. {
  563.     Range *head;
  564.     pGroup np = getGroupP( groupName );
  565.  
  566. #ifdef TRACE_ALL
  567.     printfT( "TNewsrc::artIsRead(%s,%ld)\n", groupName,artNum );
  568. #endif
  569.     assert( np != NULL );
  570.  
  571.     if (artNum <= 0)
  572.     return 1;
  573.     
  574.     sema.Request();
  575.     head = np->readList;
  576.     
  577.     //
  578.     //  Look through the list
  579.     //
  580.     while (head != NULL) {
  581.     if (artNum < head->lo) {
  582.         sema.Release();
  583.         return 0;
  584.     }
  585.     if (artNum >= head->lo  &&  artNum <= head->hi) {
  586.         sema.Release();
  587.         return 1;
  588.     }
  589.     head = head->next;
  590.     }
  591.     sema.Release();
  592.     return 0;
  593. }   // TNewsrc::artIsRead
  594.  
  595.  
  596.  
  597. void TNewsrc::artMarkRead( const char *groupName, long artNum )
  598. //
  599. //  Mark article as read.
  600. //
  601. {
  602.     pGroup np = getGroupP( groupName );
  603.     Range *rp, *trp, *lrp;
  604.  
  605.     if (np == NULL) {                // might be true for cross referencing
  606. #ifdef TRACE_ALL
  607.     printfT( "TNewsrc::artMarkRead(%s,%ld):  np==NULL\n",groupName,artNum );
  608. #endif
  609.     return;
  610.     }
  611.     if ( !np->subscribed)            // might be true for cross referencing
  612.     return;
  613.  
  614.     //
  615.     //  if article has been already read, do nothing...
  616.     //
  617.     if (artIsRead(groupName,artNum))
  618.     return;
  619.     
  620.     fileChanged = 1;
  621.  
  622. #ifdef TRACE_ALL
  623.     printfT( "TNewsrc::artMarkRead(%s,%ld)\n",groupName,artNum );
  624. #endif
  625.  
  626.     sema.Request();
  627.     rp = np->readList;
  628.  
  629.     /* If num is much lower than lowest range, or the list is
  630.        empty, we need new entry */
  631.     if (rp == NULL || artNum < rp->lo - 1) {
  632.     trp = new Range;
  633.     trp->lo = trp->hi = artNum;
  634.     trp->next = rp;
  635.     np->readList = trp;
  636.     sema.Release();
  637.     return;
  638.     }
  639.  
  640.     /* lrp remembers last entry in case we need to add a new entry */
  641.     lrp = NULL;
  642.  
  643.     /* Find appropriate entry for this number */
  644.     while (rp != NULL) {
  645.     /* Have to squeeze one in before this one? */
  646.     if (artNum < rp->lo - 1) {
  647.         trp = new Range;
  648.         trp->lo = trp->hi = artNum;
  649.         trp->next = rp;
  650.         lrp->next = trp;
  651.         sema.Release();
  652.         return;
  653.     }
  654.  
  655.     /* One less than entry's lo? */
  656.     if (artNum == rp->lo - 1) {
  657.         rp->lo = artNum;
  658.         sema.Release();
  659.         return;
  660.     }
  661.  
  662.     /* In middle of range, do nothing */
  663.     if (artNum >= rp->lo && artNum <= rp->hi) {
  664.         sema.Release();
  665.         return;
  666.     }
  667.  
  668.     /* One too high, must check if we merge with next entry */
  669.     if (artNum == rp->hi + 1) {
  670.         if (rp->next != NULL && artNum == rp->next->lo - 1) {
  671.         trp = rp->next;
  672.         rp->hi = trp->hi;
  673.         rp->next = trp->next;
  674. ////        delete trp;
  675.         sema.Release();
  676.         return;
  677.         } else {
  678.         /* No merge */
  679.         rp->hi = artNum;
  680.         sema.Release();
  681.         return;
  682.         }
  683.     }
  684.  
  685.     lrp = rp;
  686.     rp = rp->next;
  687.     }
  688.  
  689.     /* We flew off the end and need a new entry */
  690.     trp = new Range;
  691.     trp->lo = trp->hi = artNum;
  692.     trp->next = NULL;
  693.     lrp->next = trp;
  694.  
  695. #ifdef TRACE_ALL
  696.     printfT( "TNewsrc::artMarkRead\n" );
  697. #endif
  698.     sema.Release();
  699.     return;
  700. }   // TNewsrc::artMarkRead
  701.  
  702.  
  703.  
  704. void TNewsrc::grpCatchup( const char *groupName, long groupLo, long groupHi, long numKeep )
  705. {
  706.     long lo;
  707.  
  708. #ifdef TRACE_ALL
  709.     printfT( "TNewsrc::grpCatchup(%s,%ld,%ld,%ld)\n",groupName,groupLo,groupHi,numKeep );
  710. #endif
  711.     lo = groupHi - numKeep + 1;
  712.     if (lo < 1)
  713.     lo = 1;
  714.     if (lo < groupLo)
  715.     lo = groupLo;
  716.     grpFixReadList( groupName, lo, groupHi );
  717.     fileChanged = 1;
  718. }   // TNewsrc::grpCatchup
  719.  
  720.  
  721.  
  722. void *TNewsrc::grpAdd( const char *groupName, int subscribe )
  723. //
  724. //  Adds new group name (in any case).  This means:  double entries are possible,
  725. //  so check with grpExists() prior to the call to grpAdd()
  726. //
  727. {
  728.     pGroup np;
  729.     unsigned hasho;
  730.  
  731.     sema.Request();
  732.  
  733.     //
  734.     //  allocate new entry
  735.     //
  736.     np = new Group;
  737.     np->subscribed = subscribe;
  738.     np->readList = NULL;
  739.     np->name = xstrdup(groupName);
  740.     np->next = NULL;
  741.  
  742.     //
  743.     //  update hash list
  744.     //
  745.     hasho = hashi(groupName,NEWSRC_HASHSIZE);
  746.     np->hashNext = hashTab[hasho];
  747.     hashTab[hasho] = np;
  748.  
  749.     //
  750.     //  add new group to end of list
  751.     //
  752.     if (groups == NULL)
  753.     groups = np;
  754.     else {
  755.     pGroup pp = (addGroupP == NULL) ? groups : addGroupP;
  756.     while (pp->next != NULL)
  757.         pp = pp->next;
  758.     pp->next = np;
  759.     addGroupP = np;
  760.     }
  761.  
  762.     fileChanged = 1;
  763.  
  764.     sema.Release();
  765.     return (void *)np;
  766. }   // TNewsrc::grpAdd
  767.