home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / vsoup128.zip / news.cc < prev    next >
C/C++ Source or Header  |  1997-04-20  |  48KB  |  1,891 lines

  1. //  $Id: news.cc 1.44 1997/04/20 19:18:04 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@swol.de.
  6. //
  7. //  This file is part of VSoup for OS/2.  VSoup 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. //  Get news from NNTP server.
  12. //
  13. //  rg270596:
  14. //  - multithreading support for OS/2
  15. //
  16. //  Und nun mal ein paar Erfahrungen mit multithreading (in diesem Zusammenhang):
  17. //  -----------------------------------------------------------------------------
  18. //
  19. //  Probleme mit CHANGI:
  20. //  - die ursprüngliche CHANGI Version "spann" bei vielfachem Zugriff
  21. //    Update nach changi09m brachte erhebliche Erleichterung
  22. //  - nntp-NEXT tut nicht, wenn gleichzeitig ein Artikel gelesen wird,
  23. //    oder wenn mehrere NEXTs unterwegs sind (könnte CHANGI-Problem sein)
  24. //    120696:  war eher meine Blödheit und hatte u.U. was mit den Signalen zu tun...
  25. //             Wahrscheinlich braucht der NEXT recht lange und der entsprechende
  26. //             gets() wurde dann mit hoher Wahrscheinlichkeit durch ein Signal
  27. //             unterbrochen...
  28. //
  29. //  Probleme mit EMX-GCC09a:
  30. //  - das signal-handling scheint fragwürdig (signal muß aber abgefangen werden...):
  31. //    - nextchar in socket (wird durch recv/read gemacht) kommt u.U. mit EINTR
  32. //      zurück - doch wie setze ich dann wieder auf???
  33. //    - _beginthread kommt u.U. mit EINVAL zurück, was jedoch ebenso auf einen
  34. //      unterbrochenen Aufruf schließen läßt (d.h. Fehlerauswertung nicht vollständig)
  35. //    --> Signale nicht dazu verwenden, um die Beendigung eines Threads anzuzeigen,
  36. //        sondern nur in absoluten Notfällen!!!
  37. //  - einmal (?) hatte ich im (durch Semaphor geschützten) StdOut einen größeren
  38. //    Block doppelt.  Das Programm hat den Block unmöglich (?) produzieren können,
  39. //    also kommt nur EMX-GCC in Frage bzw. das OS
  40. //  - new/delete können nicht selbst definiert werden
  41. //  - wie komme ich bitte an _threadid ? (stddef.h war nicht für C++)
  42. //  - unlink steht nicht in stdio.h, sondern unistd.h
  43. //  - tmpfile() / tempnam() durch Semaphor geschützt ??
  44. //  --> hätte ich Zugriff, würde ich sofort nach 09b updaten!!!
  45. //
  46. //  Hausgemachte Probleme:
  47. //  - ein mehrfacher Request von einem MutexSemaphor in EINEM Thread hält diesen
  48. //    NICHT an.  Nur ein anderer Thread kann das Semaphor nicht mehr anfordern...
  49. //  - Zustandsmaschine war durch 'mode reader' nicht mehr korrekt (es wurde schon ein
  50. //    'waiting' angezeigt, obwohl noch 'init' war...)
  51. //    - in nntpMtGetFinished wurde der Zustand zweimal abgefragt und dann noch in der
  52. //      Reihenfolge 'finished'?, 'running'?.  Dieser Übergang wird aber in einem Thread
  53. //      gemacht -> Thread war u.U. noch nicht 'finished', aber auch nicht mehr 'running'.
  54. //      Dies ergibt ein leicht inkonsistentes Bild der Zustände!
  55. //  - wird ein Zähler im Thread hochgezählt und muß hinterher ausgewertet
  56. //    werden, so empfiehlt sich mindestens ein Semaphor (vielleicht auch noch
  57. //    volatile) (bytesRcvd)
  58. //  - die Threads müssen auch einen Signal-Handler für z.B. SIGPIPE haben, sonst
  59. //    gibt es bei Abbruch u.U. einen doppelten Fehler! (das kommt daher, wie ein
  60. //    Programm abgebrochen wird)
  61. //  - ein Event-Semaphor will auch zurückgesetzt werden!  Die 'Kinder' laufen sonst
  62. //    echt Gefahr zu verhungern...
  63. //  - stream-I/O muß konsequent durch MuxSema geschützt werden (ein bißchen Disziplin
  64. //    bitte)
  65. //  - regexp hat statische Variablen
  66. //  - um Klassen, die was mit Listen oder so zu tun haben, am besten auch ein
  67. //    individuelles Semaphor legen
  68. //  - stimmt der makefile nicht, und es wird ein Datentyp geändert, so kommt es
  69. //    klarerweise zu seltsamen Effekten (die Objekte werden nicht neu angelegt, etc.)
  70. //  - beachte Zuweisung eines 'char' von 0xff (== -1) an einen int!!!
  71. //
  72.  
  73.  
  74. #include <assert.h>
  75. #include <process.h>
  76. #include <signal.h>
  77. #include <stdlib.h>
  78. #include <string.h>
  79. #include <unistd.h>
  80. #include <sys/nls.h>
  81.  
  82. #include "areas.hh"
  83. #include "global.hh"
  84. #include "kill.hh"
  85. #include "mts.hh"
  86. #include "news.hh"
  87. #include "newsrc.hh"
  88. #include "nntp.hh"
  89. #include "nntpcl.hh"
  90. #include "socket.hh"
  91.  
  92.  
  93.  
  94. static TSemaphor   xhdrSema;
  95. static TSemaphor   syncTransSema;
  96. static TEvSemaphor threadFinito;
  97. static TEvSemaphor disconnectDone;
  98.  
  99. static TProtCounter artsRcvd;         // articles received...
  100. static TProtCounter artsKilled;       // articles killed...
  101. static TProtCounter artsTot;          // total number of articles (just for displaying)
  102. static TProtCounter artsGotten;       // estimated number of gotten articles
  103. static TProtCounter activeRoutines;   // used by mtGetGroup()
  104.  
  105. static TKillFile killF;               // kill file handling
  106.  
  107. static volatile int doingProcessSendme;        // currently doing processSendme
  108. static volatile int stopOperation = 0;         // we are in the finished loop - stop all unimportant ops (getXhdr)
  109. static volatile int abortOperation = 0;        // absolutely finish
  110.  
  111. static long catchupNumKeep;
  112.  
  113. //
  114. //  thread states (init must be 0)
  115. //  starting is for debugging and not absolutely required...
  116. ////  (are static vars initialized to zero ?)
  117. //
  118. enum NntpStates { init,connecting,failed,waiting,starting,
  119.           running,runningspecial,aborted };
  120.  
  121. //
  122. //  these are the thread connections to the news server
  123. //
  124. static TNntp nntp[ MAXNNTPTHREADS ];             // no pointer (because of imlicit destructor)
  125. static volatile NntpStates nntpS[ MAXNNTPTHREADS ] = {init};
  126. static volatile int nntpSyncCall[ MAXNNTPTHREADS ];
  127.  
  128.  
  129.  
  130. //--------------------------------------------------------------------------------
  131. //
  132. //  utility functions
  133. //
  134.  
  135.  
  136.  
  137. #if !defined(NDEBUG)
  138. static void printThreadState( const char *pre, int maxNo=maxNntpThreads )
  139. {
  140.     int i;
  141.     char b[100];
  142.  
  143.     assert( maxNo <= maxNntpThreads );
  144.  
  145.     sprintfT( b,"%s: ",pre );
  146.     for (i = 0;  i < maxNo;  i++) {
  147.     switch (nntpS[i]) {
  148.     case init:
  149.         strcat(b,"[i]");
  150.         break;
  151.     case connecting:
  152.         strcat(b,"[c]");
  153.         break;
  154.     case starting:
  155.         strcat(b,"[s]");
  156.         break;
  157.     case waiting:
  158.         strcat(b,"[w]");
  159.         break;
  160.     case running:
  161.         strcat(b,"[r]");
  162.         break;
  163.     case runningspecial:
  164.         strcat(b,"[R]");
  165.         if (maxNo < maxNntpThreads)
  166.         ++maxNo;
  167.         break;
  168.     case failed:
  169.         strcat(b,"[E]");
  170.         if (maxNo < maxNntpThreads)
  171.         ++maxNo;
  172.         break;
  173.     case aborted:
  174.         strcat(b,"[A]");
  175.         if (maxNo < maxNntpThreads)
  176.         ++maxNo;
  177.         break;
  178.     }
  179.     }
  180.     printfT( "%s\n",b );
  181. }
  182. #endif
  183.  
  184.  
  185.  
  186. static long killArticleQ( const char *groupName, const char *headerLine )
  187. //
  188. //  this is a hook function for TNntp...
  189. //  Attention:  groupName/headerLine are NULL, if killQHook is called the first
  190. //              time for the current header!
  191. //
  192. {
  193.     return killF.matchLine( groupName, headerLine );
  194. }   // killArticleQ
  195.  
  196.  
  197.  
  198. static void processXref( const char *s )
  199. //
  200. //  Process an Xref line.
  201. //  format: 'Xref: '<host-name> <grp-name[: ]grp-num>(\b<grp-name[: ]grp-num>)*
  202. //  - s points behind 'Xref: '
  203. //  - \b may be blank or \t
  204. //  
  205. //  rg260596:  the new version works with sscanf (before strtok).  Hopefully this version
  206. //             is ok for multithreading
  207. //
  208. //  hook function for TNntp...
  209. //
  210. //
  211. {
  212.     const char *p, name[FILENAME_MAX];
  213.     int num, cnt;
  214.  
  215. #ifdef DEBUG_ALL
  216.     printfT( "XREF: '%s'\n",s );
  217. #endif
  218.  
  219.     //
  220.     //  Skip the host field
  221.     //
  222.     p = strpbrk( s," \t" );
  223.     if (p == NULL)
  224.     return;
  225.  
  226.     //
  227.     //  Look through the rest of the fields
  228.     //  (note:  the %n does not count in the sscanf-result)
  229.     //
  230.     while (sscanfT(p,"%*[ \t]%[^ \t:]%*[ \t:]%d%n",name,&num,&cnt) == 2) {
  231. #ifdef DEBUG_ALL
  232.     printfT( "xref: '%s' %d\n",name,num );
  233. #endif
  234.     newsrc.artMarkRead( name,num );
  235.     p += cnt;
  236.     }
  237. }   // processXref
  238.  
  239.  
  240.  
  241. static int writeArticle( TAreasMail &msgF, TFile &inF, const char *groupName )
  242. //
  243. //  Copy article from temporary file to TAreas-msgfile.
  244. //  Current file pointer points to end of article
  245. //  Return TRUE if article was copied (successfully).
  246. //
  247. {
  248.     long artSize;
  249.     long toRead, wasRead;
  250.     char buf[4096];   // 4096 = good size for file i/o
  251.     int  res;
  252.  
  253.     //
  254.     //  Get article size.
  255.     //
  256.     artSize = inF.tell();
  257. #ifdef DEBUG_ALL
  258.     if (artSize <= 0)
  259.     printfT( "writeArticle(): ftellT: %p,%ld,%ld\n",&inF,artSize,inF.tell() );
  260. #endif
  261.     if (artSize <= 0)
  262.     return 0;    // Skip empty articles
  263.  
  264.     msgF.msgStart( groupName,"Bn" );
  265.     
  266.     //
  267.     //  Copy article body.
  268.     //
  269.     inF.seek(0L, SEEK_SET);
  270.     res = 1;
  271.     while (artSize > 0) {
  272.     toRead = ((size_t)artSize < sizeof(buf)) ? artSize : sizeof(buf);
  273.     wasRead = inF.read(buf, toRead);
  274.     if (wasRead != toRead) {
  275.         perror("read article");
  276. #ifdef DEBUG_ALL
  277.         printfT( "writeArticle: read article: %p,%lu,%lu,%p,%lu,%lu\n",buf,toRead,wasRead,&inF,inF.tell(),artSize );
  278. #endif
  279.         res = 0;
  280.         break;
  281.     }
  282.     assert( wasRead > 0 );
  283.     if (msgF.msgWrite(buf, wasRead) != wasRead) {
  284.         perror("write article");
  285.         res = 0;
  286.         break;
  287.     }
  288.     artSize -= wasRead;
  289.     }
  290.  
  291.     msgF.msgStop();
  292.     return res;
  293. }   // writeArticle
  294.  
  295.  
  296.  
  297. static void mtThroughputInfo( void *arg )
  298. {
  299.     int sleepMs = (int)arg;
  300.     int cnt = 0;
  301.     unsigned char rotate[] = {196,192,179,218,196,191,179,217};
  302.     long arts, ks, got, tot, bytes, oldBytes;
  303.     char msg[80];
  304.  
  305. #if defined(DEBUG)  ||  defined(DEBUG_ALL)
  306.     return;
  307. #endif
  308. #ifdef WININITIALIZE
  309.     WinInitialize(0);
  310. #endif
  311. #if defined(OS2)  &&  defined(__MT__)
  312.     DosSetPriority(PRTYS_THREAD, PRTYC_NOCHANGE,5, 0);
  313. #endif
  314.  
  315.     oldBytes = 0;
  316.     for (;;) {
  317.     bytes = TSocket::getBytesRcvd();
  318.     if (bytes != oldBytes)
  319.         ++cnt;
  320.     else
  321.         cnt ^= 1;
  322.     oldBytes = bytes;
  323.  
  324.     ks   = bytes / 1000;
  325.     arts = artsRcvd;
  326.     got  = artsGotten;
  327.     tot  = artsTot;
  328. #ifdef DEBUG
  329.     areas.mailPrintf1( 0,"%%: %ld %ld %ld %ld%%\n", arts, got, tot,
  330.                (100*got) / ((tot != 0) ? tot : 1) );
  331.     if (got > tot)
  332.         printfT( "\n####\n" );
  333. #endif
  334.     if (tot == 0)
  335.         sprintfT( msg,"(%05ldk)", ks );
  336.     else {
  337.         if (doingProcessSendme)
  338.         sprintfT( msg,"%6ld (%ld: %05ldk)", tot, arts, ks );
  339.         else
  340.         sprintfT( msg,"%3d%% (%ld: %05ldk)",(int)((100*got) / tot), arts, ks );
  341.     }
  342.     printfT( "\r%c  %-40s\r",rotate[cnt % sizeof(rotate)],msg );
  343.     _sleep2( sleepMs );
  344.     if (abortOperation  ||  doAbortProgram)
  345.         break;
  346.     }
  347.     printfT( "\r%-45s\r","" );
  348. }   // mtThroughputInfo
  349.  
  350.  
  351.  
  352. static int enoughRcvdQ()
  353. //
  354. //  returns '1' if we have read enough
  355. //
  356. {
  357.     static int msgDisplayed = 0;
  358.     int res = 0;
  359.  
  360.     if (maxBytes > 0  &&  TSocket::getBytesRcvd() >= maxBytes) {
  361.     blockThread();
  362.     if ( !msgDisplayed) {
  363.         msgDisplayed = 1;
  364.         unblockThread();
  365.         areas.mailPrintf1( 1,"%s: ok, we've read enough...\n",
  366.                    progname );
  367.     }
  368.     else
  369.         unblockThread();
  370.     res = 1;
  371.     }
  372.     return res;
  373. }   // enoughRcvdQ
  374.  
  375.  
  376.  
  377. //--------------------------------------------------------------------------------
  378.  
  379.  
  380.  
  381. #if defined(OS2)  &&  defined(__MT__)
  382. static void signalHandlerThread( int signo )
  383. {
  384. #ifndef NDEBUG
  385.     printfT( "\nthread received signal %d\n",signo );
  386. #endif
  387.     signal( signo, SIG_DFL );
  388.     _endthread();
  389. }   // signalHandlerThread
  390. #endif
  391.  
  392.  
  393.  
  394. static void mtInitSignals( void )
  395. //
  396. //  very important to call this function during init of a THREAD ////???
  397. //
  398. {
  399. #ifdef TRACE_ALL
  400.     printfT( "mtInitSignals()\n" );
  401. #endif
  402. #if defined(OS2)  &&  defined(__MT__)
  403.     signal(SIGHUP,   signalHandlerThread );     // hang-up
  404.     signal(SIGPIPE,  signalHandlerThread );     // broken pipe
  405.     signal(SIGSEGV,  signalHandlerThread );
  406.     signal(SIGTERM,  signalHandlerThread );     // kill (läßt der sich doch catchen?)
  407.     signal(SIGUSR1,  signalHandlerThread );
  408. #endif
  409. }   // mtInitSignals
  410.  
  411.  
  412.  
  413. static void mtGetArticle( void *threadNo )
  414. //
  415. //  states:  starting -> running -> waiting|failed            ( !nntpSyncCall)
  416. //           starting -> running -> runningspecial|failed     (  nntpSyncCall)
  417. //
  418. {
  419.     int no = (int)threadNo;
  420.     TNntp::Res res;
  421.     int connectionOk = 1;
  422.  
  423. #ifdef WININITIALIZE
  424.     WinInitialize(0);
  425. #endif
  426. #ifdef TRACE_ALL
  427.     printfT( "mtGetArticle(%d): running\n",no );
  428. #endif
  429.     assert( nntpS[no] == starting );
  430.     if ( !nntpSyncCall[no])
  431.     mtInitSignals();
  432.  
  433.     //
  434.     //  get article
  435.     //
  436.     nntpS[no] = running;
  437.     res = nntp[no].getArticle();
  438.     ++artsGotten;
  439.  
  440.     //
  441.     //  if successfully retrieved, write article
  442.     //
  443.     switch (res) {
  444.     case TNntp::ok:
  445.     //
  446.     //  article successfully received
  447.     //
  448.     writeArticle( areas,nntp[no].getTmpF(),nntp[no].groupName() );
  449.     newsrc.artMarkRead( nntp[no].groupName(), nntp[no].article() );
  450.     ++artsRcvd;
  451.     nntp[no].artNotAvail = 0;
  452.     break;
  453.     case TNntp::killed: {
  454.     char msg[200];
  455.     
  456.     newsrc.artMarkRead( nntp[no].groupName(), nntp[no].article() );
  457.     sprintfT( msg,"%.80s:       %.80s",progname,nntp[no].getLastErrMsg() );
  458.     if (strlen(msg) >= 75 )
  459.         strcpy( msg+75, "[..]" );
  460.     areas.mailPrintf1( 1,"%s: article killed in %s:\n%s\n",progname,
  461.                nntp[no].groupName(), msg );
  462.     ++artsKilled;
  463.     nntp[no].artNotAvail = 0;
  464.     break;
  465.     }
  466.     case TNntp::notavail:
  467.     //
  468.     //  Article not available.  Look for next available article.
  469.     //
  470.     //  Should be required for syncCall's to mtGetGroup only.  Otherwise
  471.     //  not available articles should be detected by getXHdr.
  472.     //  If more than one thread is handling a single group, this next
  473.     //  handling does not (cannot) work correctly (too complicated to implement
  474.     //  correctly, expected gain too small)
  475.     //
  476.     if (doingProcessSendme) {
  477.         newsrc.artMarkRead( nntp[no].groupName(), nntp[no].article() );
  478.         areas.mailPrintf1( 1,"%s: %s: %s\n", progname,
  479.                    nntp[no].groupName(),nntp[no].getLastErrMsg() );
  480.     }
  481.     else {
  482.         //
  483.         //  hier kann was sehr häßliches passieren:
  484.         //  es wurde noch kein Article gelesen und es wird notavail
  485.         //  zurückgegeben.  Dann gibt NEXT nämlich grpLo+irgendwas
  486.         //  (den zweiten verfügbaren Artikel zurück).  Was bedeutet,
  487.         //  daß man sich mit NEXT langsam durch alle gelesenen
  488.         //  Artikel durchquält, bis man den jetzigen erreicht hat...
  489.         //  Abhilfe:  nntpArticle<0  ->  noch keinen gelesen...
  490.         //  Damit die Sache nicht bei 'kleinen' Löchern in der Newsgroup die
  491.         //  ganze Zeit in NEXTs verfällt, wird mitgezählt, wieviele Artikel
  492.         //  hintereinander nicht da waren.  Wird ein bestimmter Wert
  493.         //  überschritten, wird mit NEXT weitergearbeitet (vorher nicht!)
  494.         //
  495. #ifdef DEBUG_ALL
  496.         printfT( "mtGetArticle(%d): not avail: %ld,%d\n", no,nntp[no].article(), nntp[no].artNotAvail+1 );
  497. #endif
  498.         if (++nntp[no].artNotAvail < 10)
  499.         newsrc.artMarkRead( nntp[no].groupName(), nntp[no].article() );
  500.         else if ( !newsrc.artIsRead(nntp[no].groupName(),nntp[no].article())) {
  501.         nntp[no].artNotAvail = 0;
  502.         newsrc.artMarkRead( nntp[no].groupName(), nntp[no].article() );
  503.         if (nntp[no].nntpArticle() >= 0) {
  504.             long artLo, artHi;
  505.             long nextArt;
  506.  
  507.             artLo = nntp[no].article()+1;
  508.             artHi = nntp[no].artHi();
  509.             while (nntp[no].nextArticle(&nextArt) == TNntp::ok) {
  510.             artHi = nextArt-1;
  511. #ifdef DEBUG_ALL
  512.             printfT("mtGetArticle(%d): next! %ld-%ld  \n",no,artLo,artHi);
  513. #endif
  514.             if (nextArt > nntp[no].article())
  515.                 break;
  516.             while (artLo <= artHi)
  517.                 newsrc.artMarkRead( nntp[no].groupName(), artLo++ );
  518.             artLo = artHi+2;
  519.             }
  520.  
  521.             //
  522.             //  mark articles from artLo..artHi as read
  523.             //  at least required, if there is no next article
  524.             //
  525. #ifdef DEBUG_ALL
  526.             printfT("mtGetArticle(%d): NEXT! %ld-%ld  \n",no,artLo,artHi);
  527. #endif
  528.             while (artLo <= artHi)
  529.             newsrc.artMarkRead( nntp[no].groupName(), artLo++ );
  530.         }
  531.         }
  532.     }
  533.     break;
  534.     default:
  535.     areas.mailPrintf1( 1,"%s: %s: %s\n", progname,
  536.                nntp[no].groupName(),nntp[no].getLastErrMsg() );
  537.     connectionOk = 0;
  538.     break;
  539.     }
  540.  
  541. #ifdef TRACE_ALL
  542.     printfT( "mtGetArticle(%d): finished, %ld\n",no,nntp[no].article() );
  543. #endif
  544.     if (connectionOk) {         // article handling finished
  545.     if (nntpSyncCall[no])
  546.         nntpS[no] = runningspecial;
  547.     else
  548.         nntpS[no] = waiting;
  549.     }
  550.     else
  551.     nntpS[no] = failed;     // let connection die
  552.     threadFinito.Post();
  553. }   // mtGetArticle
  554.  
  555.  
  556.  
  557. static void mtGetNewGroups( void *threadNo )
  558. //
  559. //  states:
  560. //
  561. {
  562.     int no = (int)threadNo;
  563.     char nntpTimePath[FILENAME_MAX];
  564.  
  565. #ifdef WININITIALIZE
  566.     WinInitialize(0);
  567. #endif
  568. #ifdef TRACE_ALL
  569.     printfT( "mtGetNewGroups(%d)\n",no );
  570. #endif
  571.     assert( nntpS[no] == starting );
  572.     nntpS[no] = runningspecial;
  573.  
  574.     mtInitSignals();
  575.  
  576.     sprintfT( nntpTimePath, "%s/%s", homeDir,FN_NEWSTIME );
  577.     if (nntp[no].getNewGroups(nntpTimePath,!readOnly) != TNntp::ok) {
  578.     areas.mailPrintf1( 0,"cannot get new groups:\n\t%s\n",
  579.                nntp[no].getLastErrMsg() );
  580.     areas.mailPrintf1( 0,"\tperhaps you should check %s\n",nntpTimePath );
  581.     areas.forceMail();
  582.     nntpS[no] = waiting;
  583.     threadFinito.Post();
  584.     return;
  585.     }
  586.  
  587.     {
  588.     TFile &in = nntp[no].getTmpF();
  589.     int mailOpened = 0;
  590.     char buf[BUFSIZ];
  591.     
  592.     in.seek(0L, SEEK_SET);
  593.     
  594.     mailOpened = 0;
  595.     while (in.fgets(buf,sizeof(buf),1) != NULL) {
  596. #ifdef DEBUG_ALL
  597.         printfT( "mtGetNewGroups: %s\n",buf );
  598. #endif
  599.  
  600.         //
  601.         //  scan to see if we know about this one
  602.         //
  603.         if (newsrc.grpExists(buf))
  604.         continue;
  605.     
  606.         newsrc.grpAdd( buf );
  607.  
  608.         //
  609.         //  beim ersten neuen Namen eine Mail öffnen
  610.         //
  611.         if ( !mailOpened) {
  612.         //
  613.         //  Open message file.
  614.         //
  615.         mailOpened = 1;
  616.         areas.mailStart();
  617.         areas.mailPrintf( "new newsgroups:\n\n", buf );
  618.         areas.forceMail();                                 // force generation of status mail
  619.         }
  620.  
  621.         //
  622.         //  neuen Namen in die Mail schreiben
  623.         //
  624.         areas.mailPrintf( "%s\n",buf );
  625.     }
  626.     if (mailOpened)
  627.         areas.mailStop();
  628.     }
  629.  
  630.     nntpS[no] = waiting;
  631.     threadFinito.Post();
  632. }   // mtGetNewGroups
  633.  
  634.  
  635.  
  636. //--------------------------------------------------------------------------------
  637.  
  638.  
  639.  
  640. static int mtGetXhdrCallback( int operation, const char *line )
  641. //
  642. //  call back for each line of the XHDR command
  643. //  !! not MT safe !!
  644. //  operation:
  645. //  0   standard operation
  646. //  1   init expNum
  647. //  2   init groupName
  648. //  returns '0', if operation should be aborted (emergency exit only!)
  649. //
  650. {
  651.     long curNum;
  652.     long num;
  653.     static long expNum = 0;
  654.     static const char *groupName;
  655.  
  656. #ifdef TRACE_ALL
  657.     printfT( "mtGetXhdrCallback(%d,%s)\n",operation,line );
  658. #endif
  659.  
  660.     switch( operation ) {
  661.     case 1:
  662.     expNum = atol( line );
  663.     break;
  664.     case 2:
  665.     groupName = line;
  666.     break;
  667.     default:
  668.     curNum = atol( line );
  669.     
  670.     for (num = expNum;  num < curNum;  num++) {
  671.         newsrc.artMarkRead( groupName,num );
  672. #ifdef DEBUG_ALL
  673.         printfT( "xhdr %ld not available\n",num );
  674. #endif
  675.     }
  676.     expNum = curNum + 1;
  677.     }
  678.     return !stopOperation;
  679. }   // mtGetXhdrCallback
  680.  
  681.  
  682.  
  683. static void mtGetXhdr( void *threadNo )
  684. //
  685. //  Get XHDRs if supported
  686. //  If successful, the obtained information is used to remove not available
  687. //  articles from newsrc.  Assumption is, that newsserver returns this info
  688. //  in rising order
  689. //
  690. //  *** I am not sure, if this option is really useful ***
  691. //
  692. {
  693.     int no = (int)threadNo;
  694.     TNntp::Res res;
  695.  
  696. #ifdef WININITIALIZE
  697.     WinInitialize(0);
  698. #endif
  699. #ifdef TRACE_ALL
  700.     printfT("mtGetXhdr(%d)\n",no );
  701.     printThreadState("mtGetXhdr");
  702. #endif
  703.  
  704.     xhdrSema.Request();                // weil es die Vars des Callback nur einmal gibt
  705.  
  706.     assert( nntpS[no] == starting );
  707.     nntpS[no] = runningspecial;
  708.  
  709.     mtInitSignals();
  710.  
  711.     res = TNntp::ok;
  712.     if (nntp[no].artHi() - nntp[no].artFirst() > 20)
  713.     res = nntp[no].getXhdr( "LINES",nntp[no].artFirst(),nntp[no].artHi(),
  714.                 mtGetXhdrCallback );
  715.  
  716.     if (res == TNntp::ok)
  717.     nntpS[no] = waiting;
  718.     else
  719.     nntpS[no] = aborted;
  720.  
  721.     threadFinito.Post();
  722.  
  723.     xhdrSema.Release();
  724. }   // mtGetXhdr
  725.  
  726.  
  727.  
  728. //--------------------------------------------------------------------------------
  729.  
  730.  
  731.  
  732. static void _nntpMtConnect( void *threadNo )
  733. //
  734. //  set up single connection to news server (could be started as a thread)
  735. //  states:  init -> connecting -> waiting  ||
  736. //           init -> connecting -> failed
  737. //  give it three tries on problem...
  738. //
  739. {
  740.     int i;
  741.     int no = (int)threadNo;
  742.     static int readOnlyDisplayed = 0;
  743.     static int nntpMsgDisplayed = 0;
  744.  
  745. #ifdef WININITIALIZE
  746.     WinInitialize(0);
  747. #endif
  748.  
  749.     assert( nntpS[no] == init );
  750.  
  751.     mtInitSignals();
  752.     nntp[no].setHelper( doXref ? processXref : NULL, killArticleQ );
  753.  
  754.     for (i = 0;  i < 3;  i++) {
  755.     nntpS[no] = connecting;
  756.  
  757.     if (doAbortProgram)
  758.         break;
  759.     
  760.     if (nntp[no].open(nntpInfo.host,nntpInfo.user,nntpInfo.passwd,nntpInfo.port) == TNntp::ok) {
  761.  
  762.         //
  763.         //  display, if posting possible or not
  764.         //
  765.         if ( !readOnlyDisplayed  &&  nntp[no].isReadOnly()) {
  766.         readOnlyDisplayed = 1;
  767.         areas.mailPrintf1( 1,"%s: you cannot post to news server %s\n",progname,nntpInfo.host );
  768.         }
  769.  
  770.         //
  771.         //  display the type of the NNTP server (sometimes useful, esp. for debugging)
  772.         //  little bit complicated, but a like nice formatting
  773.         //
  774.         if ( !nntpMsgDisplayed) {
  775.         char buf[500];
  776.         const char *p;
  777.         size_t sndx;
  778.         
  779.         nntpMsgDisplayed = 1;
  780.         sprintfT( buf,"%.450s",nntp[no].getLastErrMsg() );
  781.         strlwr( buf );    // egal, ob nls berücksichtigt wird
  782.         p = strstr(buf,"server");
  783.         sndx = 0;
  784.         sprintfT( buf,"%.450s",nntp[no].getLastErrMsg() );
  785.  
  786.         if (p != NULL) {
  787.             size_t ndx;
  788.             
  789.             ndx = p-buf;
  790.             if (ndx > 25) {
  791.             strncpy( buf+ndx-25,"...",3 );
  792.             sndx = ndx-25;
  793.             }
  794.             if (strlen(buf) > sndx+60)
  795.             strcpy( buf+sndx+57,"..." );
  796.         }
  797.         areas.mailPrintf1( 0,"%s: server: %s\n", progname,buf+sndx );
  798.         }
  799.         nntpS[no] = waiting;
  800.         break;
  801.     }
  802.     nntpS[no] = failed;
  803.     }
  804. }   // _nntpMtConnect
  805.  
  806.  
  807.  
  808. static void nntpConnect( int maxThreads )
  809. //
  810. //  set up connection to news server
  811. //
  812. {
  813.     int i;
  814.  
  815. #ifdef TRACE_ALL
  816.     printfT( "nntpConnect(%d)\n",maxThreads );
  817. #endif
  818.  
  819.     assert( maxThreads <= maxNntpThreads );
  820.  
  821.     for (i = 0;  i < maxThreads;  i++)
  822.     BEGINTHREAD( _nntpMtConnect, (void *)i );
  823. }   // nntpConnect
  824.  
  825.  
  826.  
  827. static void nntpMtDisconnect( void *maxThreads )
  828. //
  829. //  (explicit) disconnect from news server
  830. //
  831. {
  832.     int maxNo = (int)maxThreads;
  833.     int i;
  834.     
  835. #ifdef WININITIALIZE
  836.     WinInitialize(0);
  837. #endif
  838. #ifdef TRACE_ALL
  839.     printfT( "nntpDisconnect(%d)\n",maxNo );
  840. #endif
  841.  
  842.     assert( maxNo <= maxNntpThreads );
  843.  
  844.     mtInitSignals();
  845.     for (i = 0;  i < maxNo;  i++) {
  846.     if (doAbortProgram)
  847.         break;
  848.     if (nntpS[i] != failed  &&  nntpS[i] != aborted)
  849.         nntp[i].close();
  850.     nntpS[i] = init;
  851.     }
  852.     disconnectDone.Post();
  853. }   // nntpDisconnect
  854.  
  855.  
  856.  
  857. static int nntpMtWaitConnect( int maxThreads=maxNntpThreads )
  858. //
  859. //  wait until one of the threads has successfully connected, or all of them
  860. //  have failed.  On failure return 0 (timeout after 60s)
  861. //
  862. {
  863.     int i;
  864.     int conFailed = 0;
  865.     long time = 0;
  866.  
  867.     assert( maxThreads <= maxNntpThreads );
  868.  
  869.     while ( !conFailed) {
  870. #ifdef TRACE_ALL
  871.     printThreadState( "nntpMtWaitConnect()",maxThreads );
  872. #endif
  873.     conFailed = 1;
  874.     for (i = 0;  i < maxThreads;  i++) {
  875.         switch (nntpS[i]) {
  876.         case running:
  877.         case runningspecial:
  878.         case starting:
  879.         case waiting:
  880.         return 1;                   // -> connected !
  881.         break;
  882.         case aborted:
  883.         case failed:
  884.         break;                      // -> do nothing
  885.         case init:
  886.         case connecting:
  887.         conFailed = 0;              // -> not failed
  888.         break;
  889.         }
  890.     }
  891.     //
  892.     //  wait ~100ms
  893.     //
  894.     if ( !conFailed) {
  895.         _sleep2( 100 );
  896.         time += 100;
  897.         conFailed = (time > TIMEOUT*1000);     // timeout after TIMEOUT s
  898.     }
  899.     }
  900. #ifdef TRACE_ALL
  901.     printfT( "nntpMtWaitConnect():  TIMEOUT!\n" );
  902. #endif
  903.     return 0;
  904. }   // nntpMtWaitConnect
  905.  
  906.  
  907.  
  908. static int nntpMtGetWaiting( int wait, NntpStates setState = init )
  909. //
  910. //  look for waiting thread & return ndx
  911. //  return -1, if none is waiting
  912. //  aborted/failed/runningspecial threads are skipped
  913. //  if wait requested, nntpMtGetWaiting loops til it finds a waiting thread
  914. //  (i.e. there is also no progress display)
  915. //
  916. {
  917.     int i;
  918.  
  919. #ifdef TRACE_ALL
  920.     printThreadState( "nntpMtGetWaiting()",maxNntpThreads );
  921. #endif
  922.  
  923.     for (;;) {
  924.     blockThread();
  925.     for (i = 0;  i < maxNntpThreads;  i++) {
  926.         switch (nntpS[i]) {
  927.         case waiting:
  928.         if (setState != init)
  929.             nntpS[i] = setState;
  930.         unblockThread();
  931.         return i;
  932.         default:
  933.         break;
  934.         }
  935.     }
  936.     unblockThread();
  937.     if ( !wait)
  938.         return -1;
  939.     threadFinito.Wait( 100 );
  940.     }
  941. }   // nntpMtGetWaiting
  942.  
  943.  
  944.  
  945. static int nntpMtAnyRunning( int checkSpecial, int maxThreads=maxNntpThreads )
  946. //
  947. //  return 1, if one thread is 'running', otherwise 0
  948. //  - aborted/failed threads are skipped
  949. //  - if checkSpecial is activated, the nntpMtAnyRunning return true also, if
  950. //    there is a 'runningspecial' thread.  Otherwise these threads are skipped
  951. //
  952. {
  953.     int i;
  954.  
  955. #ifdef TRACE_ALL
  956.     printThreadState( "nntpMtAnyRunning()", maxThreads );
  957. #endif
  958.  
  959.     assert( maxThreads <= maxNntpThreads );
  960.  
  961.     for (i = 0;  i < maxThreads;  i++) {
  962.     switch (nntpS[i]) {
  963.     case starting:
  964.     case running:
  965.         return 1;
  966.     case runningspecial:
  967.         if (checkSpecial)
  968.         return 1;
  969.         else if (maxThreads <= maxNntpThreads)
  970.         ++maxThreads;
  971.         break;
  972.     case aborted:
  973.     case failed:
  974.         if (maxThreads <= maxNntpThreads)
  975.         ++maxThreads;
  976.         break;
  977.     default:
  978.         break;
  979.     }
  980.     }
  981.     return 0;
  982. }   // nntpMtAnyRunning
  983.  
  984.  
  985.  
  986. static void nntpWaitFinished( int maxThreads=maxNntpThreads )
  987. //
  988. //  wait until every operation has stopped
  989. //    
  990. {
  991.     threadFinito.Wait( 500 );     // wait til mtGetGroup() has been started (in any case) - not clean
  992.     stopOperation = 1;
  993.  
  994.     for (;;) {
  995.     if ( !nntpMtAnyRunning(1,maxThreads)  &&  activeRoutines == 0)
  996.         break;
  997.     threadFinito.Wait( 500 );
  998.     }
  999.     abortOperation = 1;
  1000. }   // nntpWaitFinished
  1001.  
  1002.  
  1003.  
  1004. //--------------------------------------------------------------------------------
  1005.  
  1006.  
  1007.  
  1008. static void readNewsrc( const char *name )
  1009. {
  1010.     if ( !newsrc.readFile(name))
  1011.     areas.mailPrintf1( 1,"there is no %s file\n",name );
  1012. }   // readNewsrc
  1013.  
  1014.  
  1015.  
  1016. static int nntpConnected( void )
  1017. {
  1018.     int ok,i;
  1019.  
  1020. #ifdef TRACE_ALL
  1021.     printThreadState("nntpConnected()");
  1022. #endif
  1023.  
  1024.     ok = 0;
  1025.     for (i = 0;  i < maxNntpThreads;  ++i) {
  1026.     if (nntpS[i] == waiting  ||  nntpS[i] == aborted)   // the aborted threads were also connected successfully...
  1027.         ++ok;
  1028.     }
  1029.     return ok;
  1030. }   // nntpConnected
  1031.  
  1032.  
  1033.  
  1034. static void statusInfo( int artRead=0 )
  1035. //
  1036. //  write status info to mail file.
  1037. //  status info contains articles read/killed etc.
  1038. //
  1039. {
  1040.     int ok;
  1041.  
  1042.     if (artRead) {
  1043.     char msg1[80];
  1044.     char msg2[80];
  1045.  
  1046.     sprintfT( msg1,"%s: %ld article%s read", progname,
  1047.           (long)artsRcvd, (artsRcvd != 1) ? "s" : "" );
  1048.     msg2[0] = '\0';
  1049.     if (artsKilled != 0)
  1050.         sprintfT( msg2, ", %ld article%s killed",
  1051.               (long)artsKilled,(artsKilled != 1) ? "s" : "" );
  1052.  
  1053.     areas.mailPrintf1( 0,"\n" );
  1054.     areas.mailPrintf1( 1,"%s%s\n", msg1,msg2 );
  1055.     }
  1056.  
  1057.     ok = nntpConnected();
  1058.     areas.mailPrintf1( 1,"%s: %d thread%s %s connected successfully\n",
  1059.                progname,ok,(ok != 1) ? "s" : "",(ok != 1) ? "were" : "was" );
  1060. }   // statusInfo
  1061.  
  1062.  
  1063.  
  1064. static int checkNntpConnection( int maxThreads, const char *msg )
  1065. //
  1066. //  check connection to NNTP server
  1067. //  if failed return 0, on success return 1
  1068. //
  1069. {
  1070. #ifdef TRACE_ALL
  1071.     printfT( "checkNntpConnection(%d)\n",maxThreads );
  1072. #endif
  1073.     if ( !nntpMtWaitConnect(maxThreads)) {
  1074.     areas.mailPrintf1( 1,"%s: cannot connect to news server %s (%s):\n\t%s\n",
  1075.                progname, (nntpInfo.host != NULL) ? nntpInfo.host : "\b", msg,
  1076.                nntp[0].getLastErrMsg() );
  1077.     return 0;
  1078.     }
  1079.     areas.mailPrintf1( 1,"%s: connected to news server %s (%s)\n",
  1080.                progname,nntpInfo.host,msg );
  1081.     return 1;
  1082. }   // checkNntpConnection
  1083.  
  1084.  
  1085.  
  1086. //--------------------------------------------------------------------------------
  1087.  
  1088.  
  1089.  
  1090. static void mtGetGroup( void *threadNo )
  1091. //
  1092. //  Get articles from the newsgroup.
  1093. //  Return TRUE if successful.
  1094. //  threadNo must be an available thread, the groupName must have been
  1095. //  entered in nntp[thread]
  1096. //
  1097. {
  1098.     int thread = (int)threadNo;
  1099.     long grpCnt, grpLo, grpHi, grpFirst, artNum;
  1100.     int killEnabled;
  1101.     int artRequested;
  1102.     int somethingDone;
  1103.     const char *groupName = NULL;
  1104.     int syncCall = nntpSyncCall[thread];   // indicates synccalling of getArticle()
  1105.     int gotSyncTransSema = 0;
  1106.  
  1107. #ifdef WININITIALIZE
  1108.     WinInitialize(0);
  1109. #endif
  1110. #ifdef TRACE_ALL
  1111.     printfT( "mtGetGroup(%s), thread %d\n",nntp[thread].groupName(),thread );
  1112. #endif
  1113.  
  1114.     if (syncCall)
  1115.     mtInitSignals();
  1116.  
  1117.     ++activeRoutines;
  1118.  
  1119.     assert( thread >= 0 );
  1120.     xstrdup( &groupName, nntp[thread].groupName() );
  1121.     
  1122.     //
  1123.     //  Select group name from news server.
  1124.     //
  1125.     if (nntp[thread].setActGroup(groupName, grpCnt,grpLo,grpHi) != TNntp::ok) {
  1126.     areas.mailPrintf1( 1,"cannot select %s:\n\t%s\n\tunsubscribe group manually\n",
  1127.                groupName,nntp[thread].getLastErrMsg() );
  1128.     areas.forceMail();
  1129.     delete groupName;
  1130.     nntpS[thread] = waiting;
  1131.     --activeRoutines;
  1132.     threadFinito.Post();
  1133.     return;
  1134.     }
  1135.  
  1136.     killEnabled = killF.doKillQ( groupName );
  1137. #ifdef DEBUG
  1138.     printfT( "mtGetGroup: killEnabled=%d\n",killEnabled );
  1139. #endif
  1140.     
  1141.     //
  1142.     //  Fix the read article number list (with lo/hi received thru group selection)
  1143.     //
  1144.     newsrc.grpFixReadList( groupName,grpLo,grpHi );
  1145.  
  1146. #ifdef DEBUG_ALL
  1147.     printfT( "group selected: %s %ld-%ld\n",groupName,grpLo,grpHi );
  1148. #endif
  1149.  
  1150.     grpFirst = newsrc.grpFirstUnread( groupName,grpLo );
  1151. #ifdef DEBUG_ALL
  1152.     printfT( "first unread: %ld\n",grpFirst );
  1153. #endif
  1154.     {
  1155.     //
  1156.     //  calculate number of articles to fetch (pessimistic version)
  1157.     //  and display it.
  1158.     //
  1159.     long artCnt = grpHi-grpFirst+1;
  1160.  
  1161.     if (artCnt < 0)
  1162.         artCnt = 0;
  1163.  
  1164.     if (syncCall)                 // for display only
  1165.         artsTot += artCnt;
  1166.     else {
  1167.         blockThread();
  1168.         artsTot = (artsTot-artsGotten) + artCnt;
  1169.         artsGotten = 0;
  1170.         unblockThread();
  1171.     }
  1172. #ifdef DEBUG
  1173.     areas.mailPrintf1( 0,"%s: %ld %ld %ld %ld\n", groupName,grpFirst,grpHi,artCnt,grpCnt );
  1174. #endif
  1175.  
  1176.     if (grpHi-grpLo+1 != grpCnt  &&  artCnt > grpCnt)
  1177.         artCnt = grpCnt;
  1178.  
  1179.     areas.mailPrintf1( 1,"%s: %4ld unread article%c in %s\n", progname, artCnt,
  1180.                (artCnt == 1) ? ' ' : 's', groupName);
  1181. #ifdef DEBUG_ALL
  1182.     areas.mailPrintf1( 0,"1: %ld\n", artCnt );
  1183. #endif
  1184.     }
  1185.  
  1186.     //
  1187.     //  get the XHDRs (performace hit, if many holes in the article sequence)
  1188.     //
  1189.     if ( !syncCall) {
  1190.     nntp[thread].selectArticle( groupName,grpFirst,killEnabled,grpFirst,grpHi );
  1191.     BEGINTHREAD( mtGetXhdr, (void *)thread );   // after mtGetXHdr() thread state is waiting
  1192.     }
  1193.     
  1194.     //
  1195.     //  Look through unread articles
  1196.     //  (just a service to wait for a 'waiting' thread)
  1197.     //
  1198.     artNum = grpFirst;
  1199.     artRequested = 1;
  1200.     while (artNum <= grpHi  ||  !artRequested  ||
  1201.        (nntpMtGetWaiting(0) < 0  &&  !syncCall)) {
  1202.  
  1203.     //
  1204.     //  should we make transition from syncCall ?
  1205.     //  Note:  this should be done only by one mtGetGroup()-thread.  Otherwise
  1206.     //         it is possible that the several connected threads are changing GROUP
  1207.     //         assignment on each article (speed loss)
  1208.     //
  1209.     if (stopOperation  &&  syncCall) {
  1210. #ifdef DEBUG_ALL
  1211.         printfT( "mtGetGroup(): transition to stopOperation\n" );
  1212. #endif
  1213.         if (syncTransSema.Request(0)) {
  1214.         gotSyncTransSema = 1;
  1215.         syncCall = 0;
  1216.         nntpS[thread] = waiting;
  1217.         }
  1218.     }
  1219.  
  1220.     //
  1221.     //  find next unread article number
  1222.     //
  1223.     while (artRequested  &&  artNum <= grpHi) {
  1224.         if (newsrc.artIsRead(groupName,artNum)) {
  1225. #ifdef DEBUG_ALL
  1226.         printfT( "skip! %ld  \n",artNum );  ////
  1227. #endif
  1228.         ++artNum;
  1229.         ++artsGotten;
  1230.         }
  1231.         else
  1232.         artRequested = 0;
  1233.     }
  1234.  
  1235.     somethingDone = 0;
  1236.  
  1237.     //
  1238.     //  if there is a waiting thread, then receive the next article with that one
  1239.     //
  1240.     if ( !artRequested) {
  1241.         if ( !syncCall) {
  1242.         thread = nntpMtGetWaiting(0,starting);
  1243.         if (thread >= 0) {
  1244.             nntpSyncCall[thread] = syncCall;
  1245.             nntp[thread].selectArticle( groupName,artNum,killEnabled );
  1246.             BEGINTHREAD( mtGetArticle,(void *)thread );
  1247.             somethingDone = 1;
  1248.         }
  1249.         }
  1250.         else {
  1251.         nntpS[thread] = starting;
  1252.         nntpSyncCall[thread] = syncCall;
  1253.         nntp[thread].selectArticle( groupName,artNum,killEnabled );
  1254.         if (nntpS[thread] == failed  ||  nntpS[thread] == aborted)
  1255.             artNum = grpHi+1;
  1256.         else
  1257.             mtGetArticle( (void *)thread );
  1258.         somethingDone = 1;
  1259.         }
  1260.         if (somethingDone) {
  1261.         artRequested = 1;
  1262.         ++artNum;
  1263.         }
  1264.     }
  1265.  
  1266.     if ( !somethingDone)
  1267.         threadFinito.Wait( 500 );
  1268.  
  1269.     //
  1270.     //  Check if too many blocks already
  1271.     //
  1272.     if (enoughRcvdQ())
  1273.         artNum = grpHi+1;    // trick: initiation of article reading disabled
  1274.     }
  1275.  
  1276.     assert( artNum > grpHi );
  1277.     assert( artRequested );
  1278.     
  1279.     if (syncCall)
  1280.     nntpS[thread] = waiting;
  1281.     if (gotSyncTransSema)
  1282.     syncTransSema.Release();
  1283.  
  1284. #ifdef TRACE_ALL
  1285.     printfT( "mtGetGroup3(%s) finished\n",groupName );
  1286. #endif
  1287.  
  1288.     delete groupName;
  1289.     --activeRoutines;
  1290.     threadFinito.Post();
  1291.     return;
  1292. }   // mtGetGroup
  1293.  
  1294.  
  1295.  
  1296. //--------------------------------------------------------------------------------
  1297. //
  1298. //  handle COMMANDS file
  1299. //
  1300.  
  1301.  
  1302.  
  1303. static int processSendme( TFile &cmdF )
  1304. {
  1305.     long grpCnt, grpLo, grpHi;
  1306.     long artLo, artHi;
  1307.     int thread;
  1308.     int finished;
  1309.     int artRequested;
  1310.     int somethingDone;
  1311.     char buf[BUFSIZ];
  1312.     const char *groupName;
  1313.     int killEnabled;
  1314.  
  1315. #ifdef TRACE_ALL
  1316.     printfT( "processSendme()\n" );
  1317. #endif
  1318.  
  1319.     //
  1320.     //  Read newsgroup name.
  1321.     //
  1322.     if (cmdF.scanf("%s", buf) != 1) {
  1323.     cmdF.fgets(buf, sizeof(buf), 1);
  1324.     return 0;
  1325.     }
  1326.     groupName = xstrdup( buf );
  1327.  
  1328.     thread = nntpMtGetWaiting( 1,starting );
  1329. #ifdef TRACE_ALL
  1330.     printfT( "thread: %d\n",thread );
  1331. #endif
  1332.  
  1333.     //
  1334.     //  Select group name from news server.
  1335.     //
  1336.     if (nntp[thread].setActGroup(groupName, grpCnt,grpLo,grpHi) != TNntp::ok) {
  1337.     areas.mailPrintf1( 1,"cannot select %s:\n\t%s\n\tunsubscribe group manually\n",
  1338.                groupName,nntp[thread].getLastErrMsg() );
  1339.     areas.forceMail();
  1340.     cmdF.fgets(buf, sizeof(buf), 1);
  1341. ////    delete groupName;
  1342.     nntpS[thread] = waiting;
  1343.     return 0;
  1344.     }
  1345.     nntpS[thread] = waiting;
  1346.  
  1347.     //
  1348.     //  if group does not exist in newsrc, add it
  1349.     //
  1350.     if ( !newsrc.grpExists(groupName)) {
  1351.     newsrc.grpAdd( groupName,1 );
  1352.     areas.mailPrintf1( 1,"%s added to %s\n",groupName,newsrcFile );
  1353.     areas.forceMail();
  1354.     }
  1355.  
  1356.     //
  1357.     //  rem:  if articles are selected manually, we assume, that
  1358.     //        the user knows which article he/she selects...
  1359.     //
  1360. //    killEnabled = killF.doKillQ( groupName );
  1361.     killEnabled = 0;
  1362.  
  1363.     //
  1364.     //  Fix the read article number list
  1365.     //
  1366.     newsrc.grpFixReadList( groupName, grpLo, grpHi);
  1367.  
  1368. #ifdef DEBUG_ALL
  1369.     printfT( "group selected: %s %ld-%ld\n",groupName,grpLo,grpHi );
  1370. #endif
  1371.  
  1372.     areas.mailPrintf1( 1,"%s: %s selected\n", progname,groupName );
  1373.  
  1374.     //
  1375.     //  get the articles
  1376.     //  (just a service to wait for a 'waiting' thread)
  1377.     //
  1378.     finished = 0;
  1379.     artRequested = 1;
  1380.     artLo = artHi = 0;
  1381.     while ( !finished  ||  !artRequested  ||  nntpMtGetWaiting(0) < 0) {
  1382. #ifdef DEBUG_ALL
  1383.     printThreadState( "processSendme()" );
  1384. #endif
  1385.  
  1386.     //
  1387.     //  get next article number (if any exists)
  1388.     //
  1389.     while (artRequested  &&  !finished) {
  1390.         if (artLo < artHi) {
  1391.         ++artLo;
  1392.         if ( !newsrc.artIsRead(groupName,artLo))
  1393.             artRequested = 0;
  1394.         }
  1395.         else {
  1396.         cmdF.scanf("%*[ \t]%[0-9]", buf);
  1397.         if (buf[0] == '\0') {
  1398.             cmdF.fgets(buf, sizeof(buf), 1);
  1399.             finished = 1;
  1400.         }
  1401.         else {
  1402.             artLo = artHi = atol(buf);
  1403.             if (cmdF.scanf("-%[0-9]", buf) == 1)
  1404.             artHi = atol(buf);
  1405.             if (artLo >= 0) {
  1406.             if ( !newsrc.artIsRead(groupName,artLo))
  1407.                 artRequested = 0;
  1408.             }
  1409.         }
  1410.         }
  1411.     }
  1412.  
  1413.     somethingDone = 0;
  1414.  
  1415.     //
  1416.     //  if there is a waiting thread, then receive the article with that one
  1417.     //
  1418.     if ( !artRequested) {
  1419.         thread = nntpMtGetWaiting( 0,starting );
  1420.         if (thread >= 0) {
  1421. #ifdef DEBUG_ALL
  1422.         printfT( "sendme: %ld, fini='%d'\n",artLo,finished );
  1423. #endif
  1424.         nntp[thread].selectArticle( groupName,artLo,killEnabled );
  1425.         nntpSyncCall[thread] = 0;
  1426.         BEGINTHREAD( mtGetArticle,(void *)thread );
  1427.         artRequested = 1;
  1428.         somethingDone = 1;
  1429.         }
  1430.     }
  1431.  
  1432.     if ( !somethingDone) {
  1433.         artsTot = artLo;
  1434.         threadFinito.Wait( 500 );
  1435.     }
  1436.  
  1437.     //
  1438.     //  check if too many block already received
  1439.     //
  1440.     if (enoughRcvdQ()) {
  1441.         cmdF.fgets(buf, sizeof(buf), 1);
  1442.         finished = 1;     // trick:  stop further reading of file...
  1443.     }
  1444.     }
  1445.  
  1446. #ifdef TRACE_ALL
  1447.     printThreadState( "processSendme() finished" );
  1448. #endif
  1449.  
  1450.     assert( artRequested );
  1451. ////    delete groupName;
  1452.     return 1;
  1453. }   // processSendme
  1454.  
  1455.  
  1456.  
  1457. //--------------------------------------------------------------------------------
  1458.  
  1459.  
  1460.  
  1461. int getNews( int strategy )
  1462. //
  1463. //  If a COMMANDS file exists in the current directory, fetch the articles
  1464. //  specified by the sendme commands in the file, otherwise fetch unread
  1465. //  articles from newsgroups listed in the newsrc file.
  1466. //
  1467. //  strategy (2 is applicable only for normal fetching):
  1468. //  0:    fetch one group after the other without intersection
  1469. //  1:    already start reading next group, one thread available
  1470. //  2:    fetch groups in parallel
  1471. //  0,1:  all connected threads are receiving one group with max speed, which
  1472. //        could mean, that some threads are waiting til end of group
  1473. //  2:    all threads are kept busy
  1474. //  speed increases from 0..2 (especially for many small groups)
  1475. //  danger of receiving crossposted articles increases from 0..2
  1476. //
  1477. {
  1478.     TFile cmdF;
  1479.  
  1480.     //
  1481.     //  start connecting to nntpServer
  1482.     //
  1483.     nntpConnect( maxNntpThreads );
  1484.  
  1485.     //
  1486.     //  Read .newsrc file (may take a while)
  1487.     //
  1488.     readNewsrc(newsrcFile);
  1489.  
  1490.     //
  1491.     //  Read kill file (error msg only, if file was given thru cmdline parameter)
  1492.     //
  1493.     if (killF.readFile(killFile) == -1  &&  killFileOption) {
  1494.     areas.mailPrintf1( 1,"%s: kill file %s not found.\n", progname,killFile );
  1495.     areas.forceMail();
  1496.     }
  1497.  
  1498.     //
  1499.     //  check connection
  1500.     //
  1501.     if ( !checkNntpConnection(maxNntpThreads,"getNews"))
  1502.     return 0;
  1503.  
  1504. #ifdef DEBUG_ALL
  1505.     printfT( "waiting: %d\n", nntpMtGetWaiting(1) );
  1506. #endif
  1507.  
  1508.     //
  1509.     //  Check for new newsgroups.
  1510.     //
  1511.     if (doNewGroups) {
  1512.     int thread = nntpMtGetWaiting(1,starting);
  1513.     BEGINTHREAD( mtGetNewGroups, (void *)thread );
  1514.     }
  1515.     nntpMtGetWaiting(1);
  1516.  
  1517. #ifdef __MT__
  1518.     BEGINTHREAD( mtThroughputInfo, (void *)500 );
  1519. #endif
  1520.  
  1521.     artsRcvd = 0;
  1522.     artsKilled = 0;
  1523.  
  1524.     if (cmdF.open(FN_COMMAND,TFile::mread,TFile::otext)) {
  1525.     //
  1526.     //  Process command file containing sendme commands.
  1527.     //
  1528.     char buf[BUFSIZ];
  1529.     int  aborted = 0;
  1530.  
  1531.     doingProcessSendme = 1;
  1532.     while (cmdF.scanf("%s", buf) == 1) {
  1533.         if (stricmp(buf, "sendme") == 0) {
  1534.         processSendme(cmdF);
  1535.         while (strategy == 0  &&  nntpMtAnyRunning(0))
  1536.             threadFinito.Wait( 500 );
  1537.         }
  1538.         else {
  1539.         areas.mailPrintf1( 1,"%s: ill command in %s file: %s\n",
  1540.                    progname,FN_COMMAND,buf );
  1541.         areas.forceMail();
  1542.         cmdF.fgets(buf, sizeof(buf), 1);
  1543.         }
  1544.         if (enoughRcvdQ()) {
  1545.         aborted = 1;
  1546.         break;
  1547.         }
  1548.     }
  1549.     if ( !readOnly  &&  !aborted)
  1550.         cmdF.remove();
  1551.     else
  1552.         cmdF.close();
  1553.     } else {
  1554.     //
  1555.     //  For each subscribed newsgroup in .newsrc file
  1556.     //
  1557.     const char *groupName;
  1558.  
  1559.     doingProcessSendme = 0;
  1560.     groupName = newsrc.grpFirst();
  1561.     while (groupName != NULL) {
  1562.         int thread;
  1563.  
  1564.         assert( newsrc.grpSubscribed(groupName) );
  1565.         thread = nntpMtGetWaiting( 1,starting );
  1566.         if (enoughRcvdQ())
  1567.         break;
  1568.         nntp[thread].selectArticle( groupName );
  1569.         if (strategy == 2) {
  1570.         nntpSyncCall[thread] = 1;
  1571.         BEGINTHREAD( mtGetGroup, (void *)thread );
  1572.         }
  1573.         else {
  1574.         nntpSyncCall[thread] = 0;
  1575.         mtGetGroup( (void *)thread );
  1576.         while (strategy == 0  &&  nntpMtAnyRunning(0))
  1577.             threadFinito.Wait( 500 );
  1578.         }
  1579.         if (enoughRcvdQ())
  1580.         break;
  1581.         groupName = newsrc.grpNext( groupName );
  1582.     }
  1583.     }
  1584.  
  1585.     nntpWaitFinished();
  1586.  
  1587.     BEGINTHREAD( nntpMtDisconnect,(void *)maxNntpThreads );
  1588.  
  1589.     statusInfo(1);
  1590.  
  1591.     if ( !readOnly)
  1592.     newsrc.writeFile();
  1593.  
  1594.     disconnectDone.Wait( 5000 );    // wait for disconnect (maximum of 5s)
  1595.     return 1;
  1596. }   // getNews
  1597.  
  1598.  
  1599.  
  1600. //--------------------------------------------------------------------------------
  1601.  
  1602.  
  1603.  
  1604. static char *nextField(char **ppCur)
  1605. //
  1606. //  Return next field in record. */
  1607. //
  1608. {
  1609.     char *pEnd;
  1610.     char *pStart = *ppCur;
  1611.  
  1612.     if ((pEnd = strchr(pStart, '\t')) != NULL) {
  1613.     *pEnd++ = '\0';
  1614.     *ppCur = pEnd;
  1615.     }
  1616.     return pStart;
  1617. }   // nextField
  1618.  
  1619.  
  1620.  
  1621. static void mtSumGroup( void *threadNo )
  1622. {
  1623.     int no = (int)threadNo;
  1624.     long grpCnt,grpLo,grpHi,grpFirst;
  1625.  
  1626. #ifdef WININITIALIZE
  1627.     WinInitialize(0);
  1628. #endif
  1629. #ifdef TRACE
  1630.     printfT( "mtSumGroup(%d)\n",no );
  1631. #endif
  1632.     assert( nntpS[no] == starting );
  1633.  
  1634.     mtInitSignals();
  1635.  
  1636.     if (nntp[no].setActGroup( nntp[no].groupName(), grpCnt,grpLo,grpHi ) != TNntp::ok) {
  1637.     areas.mailPrintf1( 1,"cannot select %s (sumnews):\n\t%s\n",
  1638.                nntp[no].groupName(), nntp[no].getLastErrMsg() );
  1639.     areas.forceMail();
  1640.     goto THREAD_FINISHED;
  1641.     }
  1642.  
  1643.     //
  1644.     //  Fix up the read article number list
  1645.     //
  1646.     newsrc.grpFixReadList( nntp[no].groupName(),grpLo,grpHi );
  1647.     grpFirst = newsrc.grpFirstUnread( nntp[no].groupName(),grpLo );
  1648.     {
  1649.     //
  1650.     //  calculate number of articles to fetch (pessimistic version)
  1651.     //
  1652.     long artCnt = grpHi-grpFirst+1;
  1653.     
  1654.     if (grpHi-grpLo+1 != grpCnt) {
  1655.         if (artCnt > grpCnt)
  1656.         artCnt = grpCnt;
  1657.     }
  1658.     grpCnt = artCnt;
  1659.     }
  1660.     areas.mailPrintf1( 1,"%s: %4ld unread article%c in %s (sumnews)\n", progname, grpCnt,
  1661.                (grpCnt == 1) ? ' ' : 's', nntp[no].groupName());
  1662.  
  1663.     if (grpFirst > grpHi)
  1664.     goto THREAD_FINISHED;
  1665.  
  1666.     nntpS[no] = running;
  1667.     if (nntp[no].getOverview(grpFirst,grpHi) != TNntp::ok) {
  1668.     areas.mailPrintf1( 1,"cannot get overview of %s (sumnews):\n\t%s\n",
  1669.                nntp[no].groupName(), nntp[no].getLastErrMsg() );
  1670.     areas.forceMail();
  1671.     goto THREAD_FINISHED;
  1672.     }
  1673.  
  1674.     //
  1675.     //  write the collected data to index file
  1676.     //
  1677.     {
  1678.     TFileTmp &inF = nntp[no].getTmpF();
  1679.     char buf[BUFSIZ];
  1680.  
  1681. #ifdef TRACE_ALL
  1682.     printfT( "writing idx of %s\n",nntp[no].groupName() );
  1683. #endif
  1684.     areas.msgStart( nntp[no].groupName(), "ic" );
  1685.  
  1686.     inF.seek(0L,SEEK_SET);
  1687.     while (inF.fgets(buf,sizeof(buf),1) != NULL) {
  1688.         char *cur = buf;
  1689.         long artNum;
  1690.         char *s;
  1691.  
  1692.         artNum = atol(nextField(&cur));              // article number
  1693.         if ( !newsrc.artIsRead(nntp[no].groupName(),artNum)) {
  1694.         newsrc.artMarkRead(nntp[no].groupName(),artNum);    // avoid twice appearance of article !
  1695.         s = nextField(&cur);
  1696.         areas.msgPrintf( "\t%s\t",s );        // Subject
  1697.         s = nextField(&cur);
  1698.         areas.msgPrintf( "%s\t",s );        // From
  1699.         s = nextField(&cur);
  1700.         areas.msgPrintf( "%s\t",s );        // Date
  1701.         s = nextField(&cur);
  1702.         areas.msgPrintf( "%s\t",s );        // Message-ID
  1703.         s = nextField(&cur);
  1704.         areas.msgPrintf( "%s\t",s );        // References
  1705.         s = nextField(&cur);
  1706.         areas.msgPrintf( "0\t" );        // bytes
  1707.         s = nextField(&cur);
  1708.         areas.msgPrintf( "%s\t",s );        // lines
  1709.         areas.msgPrintf( "%ld\n",artNum);    // article number
  1710.         }
  1711.     }
  1712.     
  1713.     areas.msgStop();
  1714. #ifdef TRACE_ALL
  1715.     printfT( "writing done of %s\n",nntp[no].groupName() );
  1716. #endif
  1717.     }
  1718.  
  1719. THREAD_FINISHED:
  1720.     nntpS[no] = waiting;
  1721.     threadFinito.Post();
  1722. }   // mtSumGroup
  1723.  
  1724.  
  1725.  
  1726. static void sumGroup( const char *groupName )
  1727. //
  1728. //
  1729. {
  1730. #ifdef TRACE
  1731.     printfT( "sumGroup(%s)\n",groupName );
  1732. #endif
  1733.     if (groupName != NULL) {
  1734.     int thread = nntpMtGetWaiting( 1,starting );
  1735.     nntp[thread].selectArticle( groupName );
  1736.     BEGINTHREAD( mtSumGroup,(void *)thread );
  1737.     }
  1738.  
  1739.     while ((groupName != NULL  &&  nntpMtGetWaiting(0) < 0)  ||
  1740.        (groupName == NULL  &&  nntpMtAnyRunning(0))) {
  1741.     threadFinito.Wait( 500 );
  1742.     }
  1743. }   // sumGroup
  1744.  
  1745.  
  1746.  
  1747. int sumNews( void )
  1748. //
  1749. //  Create news summary.
  1750. //
  1751. {
  1752.     const char *groupName;
  1753.  
  1754.     //
  1755.     //  start connecting to nntpServer
  1756.     //
  1757.     nntpConnect( maxNntpThreads );
  1758.  
  1759.     //
  1760.     //  Read .newsrc file (may take a while)
  1761.     //
  1762.     readNewsrc(newsrcFile);
  1763.  
  1764.     //
  1765.     //  check connection
  1766.     //
  1767.     if ( !checkNntpConnection(maxNntpThreads,"sumNews"))
  1768.     return 0;
  1769.     nntpMtGetWaiting(1);
  1770.  
  1771. #ifdef __MT__
  1772.     BEGINTHREAD( mtThroughputInfo, (void *)500 );
  1773. #endif
  1774.  
  1775.     //
  1776.     //  For each subscribed newsgroup in the .newsrc file
  1777.     //
  1778.     groupName = newsrc.grpFirst();
  1779.     while (groupName != NULL) {
  1780.     assert( newsrc.grpSubscribed(groupName) );
  1781.     sumGroup( groupName );
  1782.     groupName = newsrc.grpNext( groupName );
  1783.     }
  1784.     sumGroup( NULL );
  1785.  
  1786.     BEGINTHREAD( nntpMtDisconnect, (void *)maxNntpThreads );
  1787.     statusInfo(0);
  1788.     if ( !readOnly)
  1789.     newsrc.writeFile();
  1790.     disconnectDone.Wait( 5000 );
  1791.     return 1;
  1792. }   // sumNews
  1793.  
  1794.  
  1795.  
  1796. //--------------------------------------------------------------------------------
  1797.  
  1798.  
  1799.  
  1800. static void mtCatchup( void *threadNo )
  1801. {
  1802.     int no = (int)threadNo;
  1803.     long grpCnt,grpLo,grpHi;
  1804.  
  1805. #ifdef WININITIALIZE
  1806.     WinInitialize(0);
  1807. #endif
  1808. #ifdef TRACE
  1809.     printfT( "mtCatchup(%d)\n",no );
  1810. #endif
  1811.     assert( nntpS[no] == starting );
  1812.  
  1813.     mtInitSignals();
  1814.  
  1815.     nntpS[no] = running;
  1816.  
  1817.     if (nntp[no].setActGroup( nntp[no].groupName(), grpCnt,grpLo,grpHi ) != TNntp::ok) {
  1818.     areas.mailPrintf1( 1,"cannot select %s (catchup):\n\t%s\n",
  1819.                nntp[no].groupName(), nntp[no].getLastErrMsg() );
  1820.     areas.forceMail();
  1821.     }
  1822.     else {
  1823.     //
  1824.     //
  1825.     //  catch up the read article number list
  1826.     //
  1827.     newsrc.grpCatchup( nntp[no].groupName(), 1,grpHi,catchupNumKeep );
  1828.     areas.mailPrintf1( 1,"%s: catch up %s:  %ld-%ld\n", progname,
  1829.                nntp[no].groupName(),grpLo,grpHi );
  1830.     }
  1831.  
  1832.     nntpS[no] = waiting;
  1833.     threadFinito.Post();
  1834. }   // mtCatchup
  1835.  
  1836.  
  1837.  
  1838. int catchupNews( long numKeep )
  1839. //
  1840. //  Catch up in subscribed newsgroups.
  1841. //
  1842. {
  1843.     const char *groupName;
  1844.  
  1845.     catchupNumKeep = numKeep;             // nicht besonders fein...
  1846.  
  1847.     //
  1848.     //  start connecting to nntpServer
  1849.     //
  1850.     nntpConnect( maxNntpThreads );
  1851.  
  1852.     //
  1853.     //  read .newsrc file (may take a while)
  1854.     //
  1855.     readNewsrc(newsrcFile);
  1856.  
  1857.     //
  1858.     //  check connection
  1859.     //
  1860.     if ( !checkNntpConnection(maxNntpThreads,"catchupNews"))
  1861.     return 0;
  1862.     nntpMtGetWaiting(1);
  1863.  
  1864.     //
  1865.     //  For each subscribed newsgroup in the .newsrc file
  1866.     //
  1867.     groupName = newsrc.grpFirst();
  1868.     while (groupName != NULL) {
  1869.     int thread;
  1870.  
  1871.     assert( newsrc.grpSubscribed(groupName) );
  1872.  
  1873.     thread = nntpMtGetWaiting( 1,starting );
  1874.     nntp[thread].selectArticle( groupName );
  1875.     BEGINTHREAD( mtCatchup,(void *)thread );
  1876.  
  1877.     groupName = newsrc.grpNext( groupName );
  1878.     }
  1879.  
  1880.     nntpWaitFinished();
  1881.  
  1882.     BEGINTHREAD( nntpMtDisconnect, (void *)maxNntpThreads );
  1883.     statusInfo(0);
  1884.  
  1885.     if ( !readOnly)
  1886.     newsrc.writeFile();
  1887.  
  1888.     disconnectDone.Wait( 5000 );
  1889.     return 1;
  1890. }   // catchupNews
  1891.