/*
 *   THIS FILE IS UNDER RCS - DO NOT MODIFY UNLESS YOU HAVE
 *   CHECKED IT OUT USING THE COMMAND CHECKOUT.
 *
 *    $Id: ora_trace_save.c,v 1.19 2002/06/07 16:58:25 patton Exp $
 *
 *    Revision history:
 *     $Log: ora_trace_save.c,v $
 *     Revision 1.19  2002/06/07 16:58:25  patton
 *     Made logit changes.
 *
 *     Revision 1.18  2001/12/04 19:54:45  lucky
 *     Fixed so that we attach to the ring before we beat our heart for the first time.
 *
 *     Revision 1.17  2001/11/27 23:09:38  alex
 *     put out first heartbeat to register pid with statmgr
 *
 *     Revision 1.16  2001/08/11 22:34:08  lucky
 *     removed a bad logit call which printed character as %d.
 *
 *     Revision 1.15  2001/07/01 21:55:27  davidk
 *     Cleanup of the Earthworm Database API and the applications that utilize it.
 *     The "ewdb_api" was cleanup in preparation for Earthworm v6.0, which is
 *     supposed to contain an API that will be backwards compatible in the
 *     future.  Functions were removed, parameters were changed, syntax was
 *     rewritten, and other stuff was done to try to get the code to follow a
 *     general format, so that it would be easier to read.
 *
 *     Applications were modified to handle changed API calls and structures.
 *     They were also modified to be compiler warning free on WinNT.
 *
 *     Revision 1.14  2001/05/15 02:15:36  davidk
 *     Moved functions around between the apps, DB API, and DB API INTERNAL
 *     levels.  Renamed functions and files.  Added support for amplitude
 *     magnitude types.  Reformatted makefiles.
 *
 *     Revision 1.11  2001/02/28 17:29:10  lucky
 *     Massive schema redesign and cleanup.
 *
 *     Revision 1.10  2001/02/14 18:42:18  davidk
 *     intialized DBidChan to 0 before using it in call to EWDB_ewdb_apps_putaway_NextSnippetForAnEvent(),
 *     otherwise it might contain a value that would be mistaken for a valid idChan.
 *
 *     Revision 1.9  2000/10/12 21:12:32  lucky
 *     Modified MAXLOGO from 2 to 10
 *
 *     Revision 1.8  2000/09/29 22:09:55  alex
 *     initialized menu list structure as per Carol B's discovery. Alex
 *
 *     Revision 1.7  2000/08/09 16:38:09  lucky
 *     Lint cleanup
 *
 *     Revision 1.6  2000/07/27 16:05:31  lucky
 *     Changed MAX_BYTES_PER_EQ to DB_MAX_BYTES_PER_EQ and MAX_TRIG_BYTES to DB_MAX_TRIG_BYTES
 *
 *     Revision 1.5  2000/07/24 20:44:27  lucky
 *     Implemented global limits to module, installation, ring, and message type strings.
 *
 *     Revision 1.4  2000/07/06 16:23:47  lucky
 *     Added a check for Debug before printing an annoying log message that
 *     everything went ok with trace retrieval.
 *
 *     Revision 1.3  2000/04/14 00:13:38  alex
 *     1. Failure to stuff a snippet now generates error message via dbtsv_status.
 *     2. Removed (hopefully) redundand logit() calls, since dbtsv_status does a logit.
 *     3. Gave the SnippetMaker thread its own error text buffer, so as to not conflict with
 *     the global "Text" buffer.
 *     Alex
 *
 *     Revision 1.2  2000/03/31 21:32:44  davidk
 *     changed SnippetMaker thread so that it sets its status to -1 before
 *     killing itself.  This way the main thread should restart when the
 *     SnippetMaker dies, instead of continuing to run happily.
 *
 *     Revision 1.1  2000/03/31 17:32:54  lucky
 *     Initial revision
 *
 *     Revision 1.6  2000/03/07 19:04:02  lucky
 *     Increased MAX_WAVESERVERS to 100, and removed an annoying debug statement
 *     that printed out every single channel being compared to
 *
 *     Revision 1.5  2000/01/20 22:21:28  alex
 *     alex: changed snippet structure .eventId from integer to string,
 *     so eventId is now a string.
 *
 *     Revision 1.3  2000/01/04 20:05:29  davidk
 *     converted code from ora_trace_save.c to ora_trace_save.c to working
 *     with schema2 and later API's.
 *
 *     Revision 1.2  1999/11/09 16:42:55  lucky
 *     *** empty log message ***
 *
 *     Revision 1.1  1999/11/09 16:37:20  lucky
 *     Initial revision
 *
 *
 */

/*

   ora_trace_save.c: adapted from ora_trace_save.c to use new schema2
   and beyond API's.

   ora_trace_save.c : Cloned from dbtrace_save, which was cloned from 
     dbreport (which was written by Lynn Dietz). reads trigger messages and
     writes specified trace snippets to an Oracle db. Different from
     dbtrace_save in that it uses the API, written by Dave Kragness.

Story: we pick up TYPE_TRIG messages which are generated by various
detector algorithms. It's a verbose ascii message which we contracted
from exposure to the Menlo volcano crowd. It lists an event id, event
time, and a number of lines, each listing a station by name, trigger
start time, and duration of data to be saved for that station.
Wildcards are permitted in either the station, network or component
field (suggestion made my Steve Malone).

We loop over the lines of the message. For each line we construct an
array of implied trace requests. These had better be than the
configuration parameter MaxTrace. We then loop over this array,
requesting and disposing of each trace, on at a time.  As we do so, we
might be making premature requests: the waves might not have reached
the sensors yet. The WaveServers are smart enough to tell us if that's
the case, and how long we have to wait. So we wait for the longest wait
period, and then run over the requests again, retrieving any
'stragglers'. After that, we give up.

*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <earthworm.h>
#include <kom.h>
#include <transport.h>
#include <ws_clientII.h>
#include <mem_circ_queue.h>
#include <parse_trig.h>
#include <ewdb_ora_api.h>
#include <ewdb_apps_utils.h>
#include "ora_trace_save.h"

/* Defines 
 *********/
#define TRUE		1
#define FALSE		0

#define MAX_STR		 255
#define MAXLINE		1000
#define MAX_NAME_LEN	  50
#define MAXCOLS		 100
#define MAXTXT           150
#define MAX_WAVESERVERS   100
#define MAX_ADRLEN        20

/* Functions in this source file 
 *******************************/
void  dbtsv_config ( char * );
void  dbtsv_lookup ( void );
void  dbtsv_status ( unsigned char, short, char * );
int   dbtsv_writefile( TRACE_REQ *, long );
int match( SNIPPET* pSnppt, WS_PSCN pscn);
int duplicate( WS_PSCN pscn, TRACE_REQ* pTrReq, int nTraceReq);
thr_ret MessageStacker( void * );
thr_ret SnippetMaker( void * );

/* The message queue
 *******************/
QUEUE OutQueue;                 /* from queue.h, queue.c; sets up linked */

WS_MENU_QUEUE_REC MenuList;	/* the list of menues; the menues will be malloced! DANGER */

/* Thread things
 ***************/
#define THREAD_STACK 8192
static unsigned tidSnipper;      /* Message processing thread id */
static unsigned tidStacker;      /* Thread moving messages from transport */
                                 /*   to queue */
int MessageStackerStatus=0;      /* 0=> Stacker thread ok. <0 => dead */
int SnippetMakerStatus=0;        /* 0=> Snipper thread ok. <0 => dead */

/* Transport global variables
 ****************************/
static  SHM_INFO  Region;      /* shared memory region to use for i/o    */

#define   MAXLOGO   10
MSG_LOGO  GetLogo[MAXLOGO];    /* array for requesting module,type,instid */
short 	  nLogo;

        
/* Globals to set from configuration file
 ****************************************/
static int     RingSize;            /* max messages in output circular buffer       */
static char    RingName[MAX_RING_STR];        /* name of transport ring for i/o      */
static char    MyModName[MAX_MOD_STR];       /* speak as this module name/id        */
static int     LogSwitch;           /* 0 if no logfile should be written   */
static long    HeartBeatInt;        /* seconds between heartbeats          */
long	       TimeoutSeconds;	    /* seconds to wait for reply from ws */
long	       MaxTraces;	    /* max traces per message we'll ever deal with  */
long	       TravelTimeout;	    /* seconds to wait for wave propogation */
int	       nServer;		    /* number of wave servers we know about */
char	       wsIp[MAX_WAVESERVERS][MAX_ADRLEN];
char	       wsPort[MAX_WAVESERVERS][MAX_ADRLEN];
				    /* list of available waveServers, from config. file */

/* Things to look up in the earthworm.h tables with getutil.c functions
 **********************************************************************/
static long          RingKey;       /* key of transport ring for i/o      */
static unsigned char InstId;        /* local installation id              */
static unsigned char MyModId;       /* Module Id for this program         */
static unsigned char TypeHeartBeat; 
static unsigned char TypeError;
static unsigned char TypeTrig;

/* Error messages used by ora_trace_save 
 ***************************************/
#define  ERR_MISSMSG       0   /* message missed in transport ring       */
#define  ERR_TOOBIG        1   /* retreived msg too large for buffer     */
#define  ERR_NOTRACK       2   /* msg retreived; tracking limit exceeded */
#define  ERR_API_INIT_FAILED 3 /* ORA_API_INIT failed */
#define  ERR_XALLOC_ERROR  4   /* unable to dynamically allocate memory  */
#define  ERR_UNKNOWN       5   /* msg retreived; unkown error */
#define  ERR_QUEUE         6   /* error queueing message for sending     */
#define  ERR_MSG_STACKER   7   /* error with message stacker thread      */
#define  ERR_SNPT_MAKER    8   /* error with snippet maker thread        */
#define  ERR_WAVE_SERVER   9   /* error getting data from a wave_server  */
#define  ERR_SNIPPET2TRREQ 10  /* error in call to snippet2trReq         */
#define  ERR_ORA_STSNEVENT 11  /* error in call to DB ewdb_apps_putaway_StartSnippetEvent()*/
#define  ERR_WSGETTRACEBIN 12  /* error in call to wsGetTraceBin()       */
#define  ERR_STUFF_SNPT    13  /* can't stuff snippet into dbms          */
static char Text[MAXTXT];      /* string for log/error messages          */


/* Things to be malloc'd
 ***********************/
/* story: Some of these things don't have to be global; they are only
so that we can malloc them from main at startup time. Having them 
malloc'd by the routine which uses them presents the danger that the 
module would start ok, but die (if unable to malloc) only when the 
first event hits. Which could be much later, at a bad time...*/

char* TraceBuffer;	/* where we store the trace snippet  */
long TraceBufferLen;	/* bytes of largest snippet we're up for - from configuration file */
TRACE_REQ*	TraceReq; 	/* request forms for wave server. Malloc'd at startup. From ws_client.h */

/* Debug debug DEBUG */
 int Debug_tr_sv=0;	/* setting this to one causes all sorsts of log output */

pid_t MyPid;            /* Our own pid, sent with heartbeat for restart 
                           purposes */

main( int argc, char **argv )
{
  long         timeNow;          		/* current time                   */       
  long         timeLastBeat;     		/* time last heartbeat was sent   */
  int		i;
  char         LogFileName[128];
  
  /* Check command line arguments 
  ******************************/
  if ( argc != 2 )
  {
    fprintf( stderr, "Usage: ora_trace_save <configfile>\n" );
    exit( 0 );
  }

  /* Zero the wave server arrays 
  *****************************/
  for (i=0; i< MAX_WAVESERVERS; i++)
  {
    memset( wsIp[i], 0, MAX_ADRLEN);
    memset( wsPort[i], 0, MAX_ADRLEN);
  }
  MenuList.head=NULL; /* As per Carol Bryant. */
  MenuList.tail=NULL;

  /* Initialize name of log-file & open it 
  ***************************************/
  fprintf(stderr,"ora_trace_save:  Initializing logit.\n");
  /* Created LogFileName, to be obtained from config file name
     instead of using the fixed "ora_trace_save" for logname. */
  strcpy(LogFileName,argv[1]);
  LogFileName[strlen(argv[1])-2/*.d*/]=0;

  logit_init( LogFileName, 0, 1024, 1 );  

  /* Read the configuration file(s)
  ********************************/
  fprintf(stderr,"ora_trace_save:  Reading Config files.\n");
  dbtsv_config( argv[1] );
  logit( "" , "ora_trace_save: Read command file <%s>\n", argv[1] );

  /* Get our own Pid for restart purposes
  ***************************************/
  MyPid = getpid();
  if(MyPid == -1)
  {
		/* DK 19990715 changed from logit to fprintf since logit_init
		   not yet called **************************/
    fprintf(stderr,"ora_trace_save: Cannot get pid. Exiting.\n");
    exit(0);
  }


  /* Look up important info from earthworm.h tables
  ************************************************/
  fprintf(stderr,"ora_trace_save:  Performing Earthworm config lookup.\n");
  dbtsv_lookup();
  

  /* Attach to Input/Output shared memory ring 
  *******************************************/
  tport_attach( &Region, RingKey );

	/* Indicate whether the debugger flag is turned on. */
  logit("","Debug_tr_sv=%d\n",Debug_tr_sv);

	/* If debug mode, log our logo */
  if(Debug_tr_sv)
  {
    logit("", "ora_trace_save: Attached to public memory region %s: %d\n", 
          RingName, RingKey );
    logit("","logo:%d %u %u %u\n", nLogo,
          GetLogo[0].type,GetLogo[0].mod,GetLogo[0].instid);
  }


  /* send a heartbeat to announce our pid
  ***************************************/
  dbtsv_status( TypeHeartBeat, 0, "" ); 
  
  /* Reinitialize logit to desired logging level
  **********************************************/
  logit_init( LogFileName, 0, 1024, LogSwitch );

  /* show the wave server info
  ***************************/
  if (Debug_tr_sv ==1)
  {
    logit("","wave server stuff:\n TimeoutSeconds=%d\n",TimeoutSeconds);
    for (i=0; i< MAX_WAVESERVERS; i++)
    {
      if (wsIp[i][0]==0) 
        break;
      /*else*/
      logit(""," wsIp[%d]=.%s.",i,wsIp[i]);
      logit(""," wsPort[%d]=.%s.\n",i,wsPort[i]);
    }
  }

  
  /* Allocate the trace snippet buffer
  ***********************************/
  if ( (TraceBuffer = malloc( (size_t)TraceBufferLen)) == NULL )
  {
    sprintf(Text,"ora_trace_save: Cant allocate snippet buffer of %ld bytes. "
             "Exiting\n",TraceBufferLen);
    dbtsv_status( TypeError, ERR_XALLOC_ERROR, Text ); 

    exit(-1);
    /* shouldn't there be a status issued here at exit!?!! DK11/1998*/
  }

  /* Allocate the trace request structures 
  ***************************************/
  if ( (TraceReq = calloc( (size_t)MaxTraces, (size_t)sizeof(TRACE_REQ) ) )
       == NULL )
  {
    sprintf(Text,"ora_trace_save: Cant allocate %ld trace request structures. "
             "Exiting\n",MaxTraces);
    dbtsv_status( TypeError, ERR_XALLOC_ERROR, Text ); 

    exit(-1);
  }

  /* Initialize the disposal system
  ********************************/
  if( ewdb_api_Init(DBuser,DBpassword,DBservice ) != EWDB_RETURN_SUCCESS )
  {
    sprintf(Text, "OraAPIInit() failure; exiting!\n" );
    dbtsv_status( TypeError, ERR_API_INIT_FAILED, Text ); 
    exit( -1 );
  }

  /* Force a heartbeat to be issued in first pass thru main loop
  *************************************************************/
  timeLastBeat = time(&timeNow) - HeartBeatInt - 1;
  
  /* Create a Mutex to control access to queue
  ********************************************/
  CreateMutex_ew();  /* a single unnamed mutex? */

  /* Initialize the message queue
  *******************************/
  RingSize = 10;  /* that is, we can stack up to 10 trigger messages */

  if(initqueue(&OutQueue,(unsigned long)RingSize,
		           (unsigned long)DB_MAX_BYTES_PER_EQ))
	{
    logit( "", "Initqueue failure; exiting!\n" );
    sprintf(Text, "Initqueue failure; exiting!\n" );
    dbtsv_status( TypeError, ERR_QUEUE, Text ); 
    exit( -1 );
	}
     
  /* Start the  message stacking thread
  *************************************/
  if ( StartThread(MessageStacker, (unsigned)THREAD_STACK, &tidStacker ) == -1)
  {
    logit( "e", "ora_trace_save: Error starting  MessageStacker thread. "
           "Exiting.\n" );
    sprintf(Text, "ora_trace_save: Error starting  MessageStacker thread. "
           "Exiting.\n" );
    dbtsv_status( TypeError, ERR_MSG_STACKER, Text ); 
    tport_detach( &Region );
    exit (-1);
  }

  MessageStackerStatus=0; /*assume the best*/

  /* Start the  snippet maker thread
  **********************************/
  if ( StartThread(  SnippetMaker, (unsigned)THREAD_STACK, &tidSnipper ) == -1 )
  {
    logit( "e", "ora_trace_save: Error starting  MessageStacker thread. "
                "Exiting.\n" );
    sprintf(Text, "ora_trace_save: Error starting  MessageStacker thread. "
                "Exiting.\n" );
    dbtsv_status( TypeError, ERR_SNPT_MAKER, Text ); 
    tport_detach( &Region );
    exit (-1);
    /* shouldn't there be a status issued here at exit!?!! DK11/1998*/
  }
  SnippetMakerStatus=0; /*assume the best*/

  /* Setup done; start main loop: 
  ******************************/
  /* Having delegated message collecting, and message processing, there's
  not much for us left to do: watch thread status, and beat the heart */

  while( tport_getflag(&Region) != TERMINATE  &&
         tport_getflag(&Region) != MyPid )
    /* begin loop till Earthworm shutdown (level 1) */
  {
    /* send ora_trace_save's heartbeat
    **********************************/
    if  ( time(&timeNow) - timeLastBeat  >=  HeartBeatInt ) 
    {
      timeLastBeat = timeNow;
      dbtsv_status( TypeHeartBeat, 0, "" ); 
    }

    /* see how our threads are feeling
    **********************************/
    if ( SnippetMakerStatus < 0)
    {
      logit("et","ERROR Snippet making thread died. Exiting\n");
      sprintf(Text,"ERROR Snippet making thread died. Exiting\n");
      dbtsv_status( TypeError, ERR_SNPT_MAKER, Text ); 
      exit (-1);
    }
    if ( MessageStackerStatus < 0)
    {
      logit("et","ERROR Message stacking thread died. Exiting\n");
      sprintf(Text,"ERROR Message stacking thread died. Exiting\n");
      dbtsv_status( TypeError, ERR_MSG_STACKER, Text ); 
      exit (-1);
    }

    sleep_ew( 3000 );  	
  }					/* end of while!(shutdown requested)  (level 1) */  

  /* Termination has been requested 
  ********************************/
  tport_detach( &Region ); /* detach from shared memory */
  logit( "t", "ora_trace_save: Termination requested; exiting!\n" );
  return(0);
}
/*--------------------------------- end of main() ---------------------------------------*/


/***********************************************************************
 *  dbtsv_config() processes command file(s) using kom.c functions;    *
 *                  exits if any errors are encountered.	       *
 ***********************************************************************/
void dbtsv_config( char *configfile )
{
   int      ncommand;     /* # of required commands you expect to process   */ 
   char     init[20];     /* init flags, one byte for each required command */
   int      nmiss;        /* number of required commands that were missed   */
   char    *com;
   char    *str;
   int      nfiles;
   int      success;
   int      i;

/* Set to zero one init flag for each required command 
 *****************************************************/   
   ncommand = 13;
   for( i=0; i<ncommand; i++ )  init[i] = 0;
   nLogo = 0;
   nServer = 0;

/* Open the main configuration file 
 **********************************/
   nfiles = k_open( configfile ); 
   if ( nfiles == 0 ) {
	logit( "e",
                "ora_trace_save: Error opening command file <%s>; exiting!\n", 
                 configfile );
	exit( -1 );
   }

/* Process all command files
 ***************************/
   while(nfiles > 0)   /* While there are command files open */
   {
        while(k_rd())        /* Read next line from active file  */
        {  
	    com = k_str();         /* Get the first token from line */

        /* Ignore blank lines & comments
         *******************************/
            if( !com )           continue;
            if( com[0] == '#' )  continue;

        /* Open a nested configuration file 
         **********************************/
            if( com[0] == '@' ) {
               success = nfiles+1;
               nfiles  = k_open(&com[1]);
               if ( nfiles != success ) {
                  logit( "e", 
                          "ora_trace_save: Error opening command file <%s>; exiting!\n",
                           &com[1] );
                  exit( -1 );
               }
               continue;
            }

        /* Process anything else as a command 
         ************************************/
  /*0*/     if( k_its("LogFile") ) {
                LogSwitch = k_int();
                init[0] = 1;
            }
  /*1*/     else if( k_its("MyModuleId") ) {
                str = k_str();
                if(str) strcpy( MyModName, str );
                init[1] = 1;
            }
  /*2*/     else if( k_its("RingName") ) {
                str = k_str();
                if(str) strcpy( RingName, str );
                init[2] = 1;
            }
  /*3*/     else if( k_its("HeartBeatInt") ) {
                HeartBeatInt = k_long();
                init[3] = 1;
            }

         /* Enter installation & module to get event messages from
          ********************************************************/
  /*4*/     else if( k_its("GetEventsFrom") ) {
                if ( nLogo >= MAXLOGO ) {
                    logit( "e", 
                            "ora_trace_save: Too many <GetEventsFrom> commands in <%s>", 
                             configfile );
                    logit( "e", "; max=%d; exiting!\n", (int) MAXLOGO );
                    exit( -1 );
                }
                if( ( str=k_str() ) ) {
                   if( GetInst( str, &GetLogo[nLogo].instid ) != 0 ) {
                       logit( "e", 
                               "ora_trace_save: Invalid installation name <%s>", str ); 
                       logit( "e", " in <GetEventsFrom> cmd; exiting!\n" );
                       exit( -1 );
                   }
                   GetLogo[nLogo+1].instid = GetLogo[nLogo].instid;
                }
                if( ( str=k_str() ) ) {
                   if( GetModId( str, &GetLogo[nLogo].mod ) != 0 ) {
                       logit( "e", 
                               "ora_trace_save: Invalid module name <%s>", str ); 
                       logit( "e", " in <GetEventsFrom> cmd; exiting!\n" );
                       exit( -1 );
                   }
                   GetLogo[nLogo+1].mod = GetLogo[nLogo].mod;
                }
                if( GetType( "TYPE_TRIGLIST2K", &GetLogo[nLogo].type ) != 0 ) {
                    logit( "e", 
                               "ora_trace_save: Invalid message type <TYPE_TRIGLIST2K>" ); 
                    logit( "e", "; exiting!\n" );
                    exit( -1 );
                }
                nLogo++;
                init[4] = 1;
            }
  /*5*/     else if( k_its("DBservice") ) {
                str = k_str();
                if(str) strcpy( DBservice, str );
                init[5] = 1;
            }
  /*6*/     else if( k_its("DBpassword") ) {
                str = k_str();
                if(str) strcpy( DBpassword, str );
                init[6] = 1;
            }
  /*7*/     else if( k_its("DBuser") ) {
                str = k_str();
                if(str) strcpy( DBuser, str );
                init[7] = 1;
            }
            else if( k_its("Debug") ) {  /*optional command*/
                Debug_tr_sv=1;
            }

  /*8*/     else if( k_its("TimeoutSeconds") ) {
                TimeoutSeconds = k_int(); 
                init[8] = 1;
            }

            else if( k_its("Debug") ) {  /*optional command*/
                Debug_tr_sv=1;
            }

         /* wave server addresses and port numbers to get trace snippets from
          *******************************************************************/
  /*9*/     else if( k_its("WaveServer") ) 
		{
                if ( nServer >= MAX_WAVESERVERS ) 
		    {
                    logit( "e", 
                            "ora_trace_save: Too many <WaveServer> commands in <%s>", 
                             configfile );
                    logit( "e", "; max=%d; exiting!\n", (int) MAX_WAVESERVERS );
                    exit( -1 );
                    }
                if( ( str=k_str() ) )  strcpy(wsIp[nServer],str);
                if( ( str=k_str() ) )  strcpy(wsPort[nServer],str);
		nServer++;
		init[9]=1;
                }

  /*10*/     else if( k_its("TraceBufferLen") ) {
                TraceBufferLen = k_int() * 1000; /* convert from kilobytes to bytes */
                init[10] = 1;
            }

  /*11*/     else if( k_its("MaxTraces") ) {
                MaxTraces = k_int(); 
                init[11] = 1;
            }

  /*12*/     else if( k_its("TravelTimeout") ) {
                TravelTimeout = k_int(); 
                init[12] = 1;
            }

        /* Unknown command
         *****************/ 
	    else {
                logit( "e", "ora_trace_save: <%s> Unknown command in <%s>.\n", 
                         com, configfile );
                continue;
            }

        /* See if there were any errors processing the command 
         *****************************************************/
            if( k_err() ) {
               logit( "e", 
                       "ora_trace_save: Bad <%s> command in <%s>; exiting!\n",
                        com, configfile );
               exit( -1 );
            }
	}
	nfiles = k_close();
   }

/* After all files are closed, check init flags for missed commands
 ******************************************************************/
   nmiss = 0;
   for ( i=0; i<ncommand; i++ )  if( !init[i] ) nmiss++;
   if ( nmiss ) {
       logit( "e", "ora_trace_save: ERROR, no " );
       if ( !init[0] )  logit( "e", "<LogFile> "       );
       if ( !init[1] )  logit( "e", "<MyModuleId> "    );
       if ( !init[2] )  logit( "e", "<RingName> "      );
       if ( !init[3] )  logit( "e", "<HeartBeatInt> "  );
       if ( !init[4] )  logit( "e", "<GetEventsFrom> " );
       if ( !init[5] )  logit( "e", "<DBservice> "     );
       if ( !init[6] )  logit( "e", "<DBpassword> "    );
       if ( !init[7] )  logit( "e", "<DBuser> "        );
       if ( !init[8] )  logit( "e", "<TimeoutSeconds> "     );
       if ( !init[9] )  logit( "e", "<WaveServer> "    );
       if ( !init[10] ) logit( "e", "<TraceBufferLen> ");
       if ( !init[11] ) logit( "e", "<MaxTraces> "     );
       if ( !init[12] ) logit( "e", "<TravelTimeout> " );
       logit( "e", "command(s) in <%s>; exiting!\n", configfile );
       exit( -1 );
   }

   return;
}

/************************************************************************
 *  dbtsv_lookup( )   Look up important info from earthworm.h tables   *
 ************************************************************************/
void dbtsv_lookup( void )
{
/* Look up keys to shared memory regions
   *************************************/
   if( ( RingKey = GetKey(RingName) ) == -1 ) {
	fprintf( stderr,
 	        "ora_trace_save:  Invalid ring name <%s>; exiting!\n", RingName);
	exit( -1 );
   }

/* Look up installations of interest
   *********************************/
   if ( GetLocalInst( &InstId ) != 0 ) {
      fprintf( stderr, 
              "ora_trace_save: error getting local installation id; exiting!\n" );
      exit( -1 );
   }

/* Look up modules of interest
   ***************************/
   if ( GetModId( MyModName, &MyModId ) != 0 ) {
      fprintf( stderr, 
              "ora_trace_save: Invalid module name <%s>; exiting!\n", MyModName );
      exit( -1 );
   }

/* Look up message types of interest
   *********************************/
   if ( GetType( "TYPE_HEARTBEAT", &TypeHeartBeat ) != 0 ) {
      fprintf( stderr, 
              "ora_trace_save: Invalid message type <TYPE_HEARTBEAT>; exiting!\n" );
      exit( -1 );
   }
   if ( GetType( "TYPE_ERROR", &TypeError ) != 0 ) {
      fprintf( stderr, 
              "ora_trace_save: Invalid message type <TYPE_ERROR>; exiting!\n" );
      exit( -1 );
   }
   if ( GetType( "TYPE_TRIGLIST2K", &TypeTrig ) != 0 ) {
      fprintf( stderr, 
              "ora_trace_save: Invalid message type <TYPE_TRIGLIST2K>; exiting!\n" );
      exit( -1 );
   }
   return;
} 

/*************************************************************************
 * dbtsv_status() builds a heartbeat or error message & puts it into    *
 *                 shared memory.  Writes errors to log file & screen.   *
 *************************************************************************/
void dbtsv_status( unsigned char type, short ierr, char *note )
{
   MSG_LOGO    logo;
   char	       msg[256];
   long	       size;
   long        t;
 
/* Build the message
 *******************/ 
   logo.instid = InstId;
   logo.mod    = MyModId;
   logo.type   = type;

   time( &t );

   if( type == TypeHeartBeat )
   {
     sprintf( msg, "%ld %ld\n\0", t,MyPid);
   }
   else if( type == TypeError )
   {
	sprintf( msg, "%ld %hd %s\n\0", t, ierr, note);
	logit( "et", "ora_trace_save: %s\n", note );
   }

   size = strlen( msg );   /* don't include the null byte in the message */ 	

/* Write the message to shared memory
 ************************************/
   if( tport_putmsg( &Region, &logo, size, msg ) != PUT_OK )
   {
        if( type == TypeHeartBeat ) {
           logit("et","ora_trace_save:  Error sending heartbeat.\n" );
	}
	else if( type == TypeError ) {
           logit("et","ora_trace_save:  Error sending error:%d.\n", ierr );
	}
   }

   return;
}


/***************************************************************
 * dbtsv_writefile writes a disk file of the tracedata only    *
 ***************************************************************/
int dbtsv_writefile( TRACE_REQ *pRecord, long qid )
{

   char  fname[30];  /* Exported file name */
   FILE *fptr;
   int   ItemsWritten;

/* Generate the filename for this tracedata record 
 *************************************************/
   sprintf( fname, "%hd.%s.%s.%s", 
            (int) qid, pRecord->sta, pRecord->chan, pRecord->net );

/* Open the file  in binary mode 
 *******************************/
   fptr=fopen(fname,"w+b");
   if(fptr == NULL)
   {
      fprintf(stdout,"Trouble opening file <%s>\n",fname );
      return(EWDB_RETURN_FAILURE);
   }
   else
   {
   /* Write the Binary trace data information to the file 
    *****************************************************/
      ItemsWritten = fwrite(pRecord->pBuf, pRecord->actLen, 
                            1 /*BLOB*/, fptr );
      fclose(fptr);
   }

/* If the binary field was successfully written 
 **********************************************/
   if(ItemsWritten == 1)
   {
   /* For fun, lets dump the record header */ 
      fprintf( stdout,
              "File <%s> contains qid:%u sta:%s %s %s tracelen:%d\n",
               fname,
               (int) qid,
               pRecord->sta,
               pRecord->chan,
               pRecord->net,
               (int) pRecord->actLen );

      return(EWDB_RETURN_SUCCESS);
   }
   else
   {
      fprintf(stdout,"Trouble writing trace data to file <%s>\n",fname );

      return(EWDB_RETURN_FAILURE);
   }
}



/*******************************************************************************
* given a pointer to a snippet structure (as parsed from the trigger message)  *
* and the array of blank trace request structures, fill them with the requests *
* implied by the snippet. Don't overflow, and return the number of requests    *
*	generated                                                                    *
*	                                                                             *
* args:	pmenu: pointer to the client routine's menu list				               *
*	pSnppt:	pointer to the structure containing the parsed trigger line from the *
*		trigger message.                                 			                     *
*	pTrReq: pointer to array of trace request forms                              *
*	maxTraceReq: size of this array							                                 *
*	pnTraceReq: pointer to count of currently used trace request forms		       *
*                                                                              *
* Return Values:                                                               *
*  WS_ERR_NONE:                 Success                                        *
*  WS_ERR_SCN_NOT_IN_MENU:      SCN not found in wave_server menus             *
*  WS_ERR_BUFFER_OVERFLOW:      Too many trace requests maxTraceReq handled    *
*******************************************************************************/
int snippet2trReq(WS_MENU_QUEUE_REC* pMenuList, SNIPPET* pSnppt,
                  TRACE_REQ* pTrReq, int maxTraceReq, int* pnTraceReq )
{
  WS_MENU server = pMenuList->head;

  int ret =  WS_ERR_SCN_NOT_IN_MENU;
  WS_PSCN tank;  /* current tank */

  
  if(Debug_tr_sv==1) 
    logit("","Entering snippet2trReq\n");		

  /* Cleanup:
     this loop should be removed in favor of the bsearch() crtlb
     function, and a list of available SCN's sorted by SCN with a 
     server index instead of a list of SCN's sorted by server.
     Davidk 11/02/1998 */

  while ( server )  /* while there are servers left to process */
  {
    tank = server->pscn;

    if(Debug_tr_sv==1) 
      logit("","Searching through Server %s:%s:\n",server->addr,server->port);	
    
    while ( tank )
    {
      if ( match(pSnppt, tank)==1 && duplicate(tank, pTrReq, *pnTraceReq)==0)
      {
        ret = WS_ERR_NONE;
        strcpy( pTrReq[*pnTraceReq].sta, tank->sta  );
        strcpy( pTrReq[*pnTraceReq].net, tank->net  );
        strcpy( pTrReq[*pnTraceReq].chan,tank->chan );
        (*pnTraceReq)++;
        if( *pnTraceReq >= maxTraceReq) 
          goto overflow;
      }
      tank = tank->next;
    }  /* End while(Tank) */

    server = server->next;
  }    /* End While (server) */

  /*  This never happens.  Cleanup:  Remove it!!! 
  if ( ret == WS_ERR_EMPTY_MENU )	
    logit( "","snippet2trReq(): Empty menu\n" );
   davidk 11/16/1998 */
  
  return ret;
	
  overflow:  /* we got here because *pnTraceReq >= maxTraceReq */
    logit("","snippet2trReq: overflowed trace request array\n");
    ret = WS_ERR_BUFFER_OVERFLOW;
    return ret;
}  /* end snippet2trReq() */


/*******************************************************************************
* int match( SNIPPET* pSnppt, WS_PSCN pscn)                                    *
*                                                                              *
*	helper routine for snippet2trReq above: See if the SCN name in the Snippet   *
*	structure matches the menu item. Wildcards allowed				                   *
*******************************************************************************/
int match( SNIPPET* pSnppt, WS_PSCN pscn)
{
	int staWild =0;
	int netWild =0;
	int chanWild=0;
	
	int staMatch =0;
	int netMatch =0;
	int chanMatch=0;
	
	if (strcmp( pSnppt->sta , "*") == 0)  staWild =1;
	if (strcmp( pSnppt->chan, "*") == 0)  chanWild=1;
	if (strcmp( pSnppt->net , "*") == 0)  netWild =1;

	if ( strcmp( pSnppt->sta, pscn->sta )==0 ) staMatch=1;
	if ( strcmp( pSnppt->net, pscn->net )==0 ) netMatch=1;
	if ( strcmp( pSnppt->chan, pscn->chan )==0 ) chanMatch=1;

	if ( staWild  ==1 ||  staMatch==1)  staMatch =1;
	if ( netWild  ==1 ||  netMatch==1)  netMatch =1;
	if ( chanWild ==1 || chanMatch==1) chanMatch =1;

	if (staMatch+netMatch+chanMatch == 3) 
		return(1);
	else
	   	return(0);
}


/******************************************************************************
* int duplicate( WS_PSCN pscn, TRACE_REQ* pTrReq, int nTraceReq)              *
*                                                                             *
*	helper routine for snippet2trReq above: See if the SCN name in the Snippet  *
*	structure is a duplicate of any existing request                    		    *
******************************************************************************/
int duplicate( WS_PSCN pscn, TRACE_REQ* pTrReq, int nTraceReq)
{
  /* cleanup: this might be another good place for qsort() and bsearch()
     davidk11/1998 */
	int i;
	for (i=0; i<nTraceReq; i++)
	{
		if( strcmp( pTrReq[i].sta, pscn->sta  )==0 &&
		    strcmp( pTrReq[i].net, pscn->net  )==0 &&
		    strcmp( pTrReq[i].chan,pscn->chan )==0    ) 
			return(1); /* meaning: yes, it's a duplicate */
	}
	return(0); /* 'twas not a duplicate */
}



/********************** Message Stacking Thread *******************
 *           Move messages from transport to memory queue         *
 ******************************************************************/
thr_ret MessageStacker( void *dummy )
{
  char         *msg;           /* "raw" retrieved message               */
  int          res;
  long         recsize;        /* size of retrieved message             */
  MSG_LOGO     reclogo;        /* logo of retrieved message             */
  int          ret;

  /* Allocate space for input/output messages
  *******************************************/
  if ( ( msg = (char *) malloc(DB_MAX_BYTES_PER_EQ) ) == (char *) NULL )
  {
    logit( "e", "export_generic: error allocating msg; exiting!\n" );
    goto error;
  }

  /* Tell the main thread we're ok
  ********************************/
  MessageStackerStatus=0;

  /* Flush the ring, something a married man should never get
     caught saying.  */
  while ( tport_getmsg( &Region, GetLogo, (short)nLogo, &reclogo,
            &recsize, msg, DB_MAX_BYTES_PER_EQ-1 ) != GET_NONE );


  if(Debug_tr_sv==1) 
    logit("e",
	        "ora_trace_save:MessageStacker:starting to watch for "
	  			"trigger messages\n");

  /* Start service loop, picking up trigger messages
  **************************************************/
  while( 1 )
  {
    /* Get a message from transport ring
    ************************************/
    res = tport_getmsg( &Region, GetLogo, nLogo, &reclogo, &recsize, 
                       msg, DB_MAX_BYTES_PER_EQ-1 );
    if(Debug_tr_sv==1  && res==GET_OK) 
      logit("et","Got message from transport of %d bytes,res=%d\n",recsize,res); 

    if( res == GET_NONE ) 
    {
      /*wait if no messages for us */
      sleep_ew(100); 
      continue;
    }

    /* Check return code; report errors
    ***********************************/
    if( res != GET_OK )
    {
      if( res==GET_TOOBIG )
      {
        sprintf(Text, "msg[%ld] i%d m%d t%d too long for target",
                recsize, (int) reclogo.instid,
                (int) reclogo.mod, (int)reclogo.type );
        dbtsv_status( TypeError, ERR_TOOBIG, Text );
        continue;
      }
      else if( res==GET_MISS )
      {
        sprintf(Text, "missed msg(s) i%d m%d t%d in %s",(int) reclogo.instid,
                (int) reclogo.mod, (int)reclogo.type, RingName );
        dbtsv_status( TypeError, ERR_MISSMSG, Text );
      }
      else if( res==GET_NOTRACK )
      {
        sprintf(Text, "no tracking for logo i%d m%d t%d in %s",
                (int) reclogo.instid, (int) reclogo.mod, (int)reclogo.type,
                RingName );
        dbtsv_status( TypeError, ERR_NOTRACK, Text );
      }
      else
      {
        logit("et","ERROR:ERROR:ERROR: MsgStacker():"
                   "Unrecognized return code from tport_getmsg() %d.\n",res);
        sprintf(Text,"ERROR:ERROR:ERROR: MsgStacker():"
                   "Unrecognized return code from tport_getmsg() %d.\n",res);
        dbtsv_status( TypeError, ERR_UNKNOWN, Text );
      }
    }  /* tport_getmsg() returned !GET_OK */

    /* Queue retrieved msg (res==GET_OK,GET_MISS,GET_NOTRACK)
    *********************************************************/
    RequestMutex();
    ret=enqueue( &OutQueue, msg, recsize, reclogo ); /* put it into the queue */
    /* the SnippetMaker thread is in the biz of de-queueng and processing */
    ReleaseMutex_ew();

    if(Debug_tr_sv==1)
      logit("","Queued a message\n");
    if ( ret!= 0 )
    {
      if (ret==-2)  /* Serious: quit */
      {
        sprintf(Text,"internal queue error. Terminating.");
        dbtsv_status( TypeError, ERR_QUEUE, Text );
        goto error;
      }
      if (ret==-1)
      {
        sprintf(Text,"queue cannot allocate memory. Lost message.");
        dbtsv_status( TypeError, ERR_QUEUE, Text );
        continue;
      }
      if (ret==-3) 
      {
        sprintf(Text,"Queue full. Message lost.");
        dbtsv_status( TypeError, ERR_QUEUE, Text );
        continue;
      }
    }  /* if enqueue() failed */
    if(Debug_tr_sv==1) 
      logit("et", "stacker thread: queued msg len:%ld\n",recsize);
  } /* while(1) */

  /* we're quitting
  *****************/
  error:
    MessageStackerStatus=-1; /* file a complaint to the main thread */
    KillSelfThread(); /* main thread will restart us */
}

/******************** Message Processing Thread *******************
 *           Take messages from memory queue, create trace        *
 *	snippets, and call the disposal (PutAway) routines        *
 ******************************************************************/
/* story: the trigger message contains lines specifying 'snippets' of trace data
   (EventId, SCN, start time, and duration to save). The S,C, and N specification can
   be a *, meaning wildcard. What we do below is to loop over the lines in the message,
   creating an array of trace request structures (TRACE_REQ). We then loop over those,
   trying to extract the trace from the WaveServers. 
	As we collect, we may be making premature requests. In that case, we're told
   by the WaveServer that we're premature. We accummulate worst case wait time for any
   such stragglers.We wait this amount of time, and then request the stragglers again.	   
	This version stuffs the trace data into the Oracle server via oci. Another version
   (or maybe an option on this one), is to write various format files.
*/

thr_ret SnippetMaker( void *dummy )
{
   /* array to hold trigger message (size from earthworm.h)   */
   static char  trgMsg[DB_MAX_TRIG_BYTES];  	
   static char errText[MAXTXT];      /* string for log/error messages          */

   int          rc;
   int          i;
   long         msgSize;        /* size of retrieved message             */
   int          ret;
   SNIPPET 	    Snppt;          /* holds params for trace snippet. From parse_trig.h */

   MSG_LOGO     reclogo;        /* logo of retrieved message             */
   char*	      nxtSnippetPtr;  /* pointer to next line in trigger message */
   double	      waitForWaves;   /* wait 'till waveServers have all our stuff */
   int 		      iTrReq=0;       /* running index over TRACE_REQ structures */
   int 		      oldNTrRq=0;     /* temp for storing number of tr req's */

	 /* number of TRACE_REQ structures from the entire message*/   
   int 		      nTrReq=0;  

   EWDBid       DBidEvent,DBidWaveform,DBidChan;
   int          RetCode;
   char *       pEndString;


   	/* Tell the main thread we're ok
   	********************************/
   	SnippetMakerStatus=0;

   	/* Get message from queue
   	*************************/
   	topOfLoop:
   	RequestMutex();
   	ret=dequeue( &OutQueue, trgMsg, &msgSize, &reclogo);
   	ReleaseMutex_ew();
   	if(ret < 0 )
    { /* -1 means empty queue */
      sleep_ew(500); /* wait a bit (from 1000 to 500 on 970624:ldd) */
      goto topOfLoop;
    }
   	if( reclogo.type != TypeTrig ) /*TYPE_TRIGLIST2K*/
		{
		logit("et","ERROR: illegal message in queue\n");
		SnippetMakerStatus = -1;
		KillSelfThread();
		}
   	trgMsg[msgSize] = '\0';   /*null terminate the message*/

   	if(Debug_tr_sv==1)
      logit( "","got a message\n" );   

	/* Build the current wave server menues
	 **************************************/
	for (i=0;i< MAX_WAVESERVERS; i++)
  {	
    if ( wsIp[i][0] == 0 )
      break;
    ret=wsAppendMenu(wsIp[i],wsPort[i], &MenuList, TimeoutSeconds*1000);
    if (ret != WS_ERR_NONE )
    {
      /* lots of possible errors, as from ws_clientII.h, but for now 
      we don't care.  if things went wrong, we dont deal with that server. 
      Someday we may get fussier...*/
  		sprintf(errText,"ora_trace_save: WARNING: Error %d!  Failed to get menu from %s:%s\n",
				    ret,wsIp[i],wsPort[i]);
      dbtsv_status( TypeError, ERR_WAVE_SERVER, errText );
    }
  }  /* end for i<MAX_WAVESERVERS */
	
	/*Debug: show off our menu list
	 ******************************/
  if(Debug_tr_sv==1)   
  {
    WS_MENU server = MenuList.head; /* which points to first server's menu*/
    if( server == NULL )
    {
      logit("et","No menues from nobody! Exiting.\n");	
      exit(-1);
    }
    logit( "","\n Total Menu List:\n" );
    while ( server )
    {
      WS_PSCN tank = server->pscn;
      logit("","Server %s:%s:\n",server->addr,server->port);
      while ( tank )
      {
        logit("","%s %s %s %f %f\n", tank->sta,tank->chan,tank->net,
              tank->tankStarttime, tank->tankEndtime);
        tank = tank->next;
      }
      server = server->next;
    } 
    logit("","End of Menu\n");
  }  /*    	if(Debug_tr_sv==1)  */

  /* Initialize stuff for loop over snippets
  *****************************************/
  waitForWaves =0;
	nxtSnippetPtr = trgMsg;  /* set next snippet pointer to start of message */

	/* begin loop over lines in message    
	***********************************/
	nTrReq = 0; 	/* total number of trace requests from all snippet lines */
  logit("et","Calling parseSnippet()\n");
	while ((rc = parseSnippet(trgMsg, &Snppt, &nxtSnippetPtr)) == EW_SUCCESS) 
  {					/* get next snippet params from msg (level 1)*/
    if (Debug_tr_sv ==1)
    {
      logit("et","After parseSnippet() in while loop\n");
      logit("","\nreturn from parseSnippet: %d\n",rc);
      logit("","eventId=%s, author=%s, sta=%s, chan=%s, net=%s, "
               "startYYYYMMDD=%s, startHHMMSS=%s, starttime=%lf, duration=%d\n",
            Snppt.eventId,Snppt.author,Snppt.sta,Snppt.chan,Snppt.net,
            Snppt.startYYYYMMDD,Snppt.startHHMMSS,Snppt.starttime,
            Snppt.duration);
    }

    /* create requests implied by this snippet - ( it might include wildcards) 
    **************************************************************************/
    /* routine below will create the request structures. It has access to 
    the WaveServer Menu lists.  A little clumsiness: snippet2trReq only 
    fills in the SCN name portion of the trace request.
    The rest is done in the loop after the call.*/

    oldNTrRq=nTrReq; /* save current value of trace reqeusts */
    rc = snippet2trReq( &MenuList, &Snppt, TraceReq, MaxTraces, &nTrReq ); 
    if (rc!=WS_ERR_NONE)  /* then we've run out of request structures. */
    {  
      if(rc == WS_ERR_BUFFER_OVERFLOW)
			{
        sprintf(errText, "ora_trace_save: MaxTraces (%d) exceeded. Some traces "
                    "not saved\n",(int) MaxTraces);
			}
      else if(rc == WS_ERR_SCN_NOT_IN_MENU)
			{
        sprintf(errText, "ora_trace_save: SCN not found in menu: %s,%s,%s.\n",
              Snppt.sta,Snppt.chan,Snppt.net);
			}
      else
			{
        sprintf(errText,"ora_trace_save:SnippetMaker(): Unrecognized error (%d) "
              "from snippet2trReq().\n",rc);
			}
      dbtsv_status( TypeError, ERR_SNIPPET2TRREQ, errText );

    }  /* end if (rc!=WS_ERR_NONE) */

    if (Debug_tr_sv==1)  
      logit("","return from snippet2trReq: total of %d requests "
               "so far (was %d)\n", nTrReq,oldNTrRq);
    if (oldNTrRq == nTrReq)		
    {
      /* then this snippet generated no requests. Suspicious. Log it */
      logit("t"," ora_trace_save: WARNING: in event %s %s %s %s was either "
                "multiply requested, or not found \n",
				    Snppt.eventId,Snppt.sta,Snppt.chan,Snppt.net);
    }

    if (Debug_tr_sv==1)  
    logit("t","Preparing to loop through new TraceReqs from %d to %d for "
          "SCN %s,%s,%s\n", oldNTrRq,nTrReq,Snppt.sta,Snppt.chan,Snppt.net);
    for(iTrReq=oldNTrRq; iTrReq<nTrReq; iTrReq++)	
    /* start of loop over requests    (level 2) */
    {		
      TraceReq[iTrReq].reqStarttime = Snppt.starttime;
      TraceReq[iTrReq].reqEndtime = Snppt.starttime + Snppt.duration ;
      TraceReq[iTrReq].pBuf = TraceBuffer;
      TraceReq[iTrReq].bufLen = TraceBufferLen;
      TraceReq[iTrReq].timeout = TimeoutSeconds;

      
      if (Debug_tr_sv ==1)  /* dump the newly created request */
      {  
        logit("","Request %d:\n sta=%s, chan=%s, net=%s, reqStarttime=%lf, "
              "reqEndtime=%lf, timeout=%ld\n",
              iTrReq,TraceReq[iTrReq].sta,TraceReq[iTrReq].chan,
              TraceReq[iTrReq].net, TraceReq[iTrReq].reqStarttime,
              TraceReq[iTrReq].reqEndtime, TraceReq[iTrReq].timeout);
      }
    }	/* end of for(iTrReq < (nTrReq- oldNTrRq)) loop over requests(level 2) */
    if (Debug_tr_sv==1)  
      logit("","\n");

    if (Debug_tr_sv==1)  
      logit("","At end of while parseSnippet, TraceReq[0] is %s,%s,%s\n",
             TraceReq[0].sta,TraceReq[0].chan,TraceReq[0].net);

	}	/* end of while parseSnippet() loop over lines in message (level 1)*/

	/* Call the Put Away initializer
	********************************/
	/* This routine is responsible for initializing the scheme for disposing
	   of the trace snippets we're hopefully about to produce */

  /* We need to parse the author, so that we only pass in up to ':',
     which is the source module in earthworm, but is for our purposes,
     the end of the usable author string in general  */
  pEndString = strchr(Snppt.author,':');
  if(pEndString)
    *pEndString = 0x00;  /* NULL terminate the string */

	if(Debug_tr_sv)
		logit("","calling ewdb_apps_putaway_StartSnippetEvent("
          "Snppt.eventID = %s,  Snppt.author = %s, Debug_tr_sv = %u\n",
          Snppt.eventId, Snppt.author, Debug_tr_sv );

	RetCode = ewdb_apps_putaway_StartSnippetEvent(&DBidEvent,Snppt.eventId,
                                     Snppt.author, Debug_tr_sv );
	if(Debug_tr_sv)
  	  logit("","returning from ewdb_apps_putaway_StartSnippetEvent\n");

 	if(RetCode == EWDB_RETURN_FAILURE)
	{
    sprintf(errText,"ora_trace_save:  error in ewdb_apps_putaway_StartSnippetEvent()\n");
    dbtsv_status( TypeError, ERR_ORA_STSNEVENT, errText );
    SnippetMakerStatus=-1;
    KillSelfThread();
  }

	logit("et","ewdb_apps_putaway_StartSnippetEvent returned %d.  Preparing to "
		  "Call wsGetTraceBin() for %d Trace Reqs.\n",RetCode,nTrReq);
	/* begin loop over retrieving and disposing of the trace snippets (level 1)
	**************************************************************************/
	for ( iTrReq=0; iTrReq<nTrReq; iTrReq++)
  {
		/* get this trace; rummage through all the servers we've been told about */
		if(Debug_tr_sv)
      logit("","calling wsGetTraceBin for request %d\n",iTrReq);
		rc = wsGetTraceBin( &(TraceReq[iTrReq]), &MenuList, TimeoutSeconds*1000 );
		if (rc < 0) 
    {
      sprintf(errText," ora_trace_save: error (%d) while retrieving %s %s %s\n",
        rc,TraceReq[iTrReq].sta,TraceReq[iTrReq].chan,TraceReq[iTrReq].net); 
    dbtsv_status( TypeError, ERR_WSGETTRACEBIN, errText );
      continue;
    }
    if (rc == WS_ERR_NONE ) 
    {
		if (Debug_tr_sv == 1)
		{
	      logit("et","ora_trace_save: trace %s %s %s: went ok first time. "
            "Got %ld bytes\n",
            TraceReq[iTrReq].sta, TraceReq[iTrReq].chan, 
            TraceReq[iTrReq].net,TraceReq[iTrReq].actLen); 
		}
    }
	if (Debug_tr_sv ==1)
    {
      logit("","return from wsGetTraceBin: %d\n",rc);
      logit("","actStarttime=%lf, actEndtime=%lf, actLen=%ld, samprate=%lf\n",
            TraceReq[iTrReq].actStarttime,TraceReq[iTrReq].actEndtime,
            TraceReq[iTrReq].actLen,TraceReq[iTrReq].samprate);
    }

		/* accummulate wait times
		 ************************/
		/* We collect wait times, wait the max of those, and then issue 
       requests for those, accepting partial answers. It's assumed 
       that fancier strategies will be developed. */
		if(TraceReq[iTrReq].waitSec > 0 )
    {
		  if(Debug_tr_sv==1) 
        logit("","trace %s %s %s has wait time: %lf\n",
		          TraceReq[iTrReq].sta, TraceReq[iTrReq].chan, 
              TraceReq[iTrReq].net,TraceReq[iTrReq].waitSec);
		  if(waitForWaves< TraceReq[iTrReq].waitSec)
        waitForWaves = TraceReq[iTrReq].waitSec;
    }  /* end if(waitSec > 0) */

		/* Now call the snippet disposer
		 *******************************/
		if( TraceReq[iTrReq].waitSec == 0 ) 
    {
      /* that is, if we got all we asked for... */
      if (Debug_tr_sv ==1) 
        logit("","Calling ewdb_apps_putaway_NextSnippetForAnEvent() Snptt.eventId= %u, "
              "scn %s,%s,%s\n",
              Snppt.eventId,TraceReq[iTrReq].sta,TraceReq[iTrReq].chan,
              TraceReq[iTrReq].net);  


		/* LUCKY DEBUG: CHECK for 0 length and 0 start time */
		if ((TraceReq[iTrReq].actLen == 0) || 
					(TraceReq[iTrReq].actStarttime == 0.0))
		{
			logit ("", "DEBUG: %s-%s-%s: Bad data: actLen=%d, actStarttime=%0.2f\n",
						TraceReq[iTrReq].sta, TraceReq[iTrReq].chan, TraceReq[iTrReq].net, 
						TraceReq[iTrReq].actLen, TraceReq[iTrReq].actStarttime);
		}
		else
		{
		      /* Set DBidChan to 0, so that it won't be taken as a valid idChan */
		      DBidChan = 0;

		      /* Call ewdb_apps_putaway_NextSnippetForAnEvent() */
		      rc = ewdb_apps_putaway_NextSnippetForAnEvent(DBidEvent, &(TraceReq[iTrReq]),
                                      &DBidWaveform,&DBidChan);
      
		      if (rc == EWDB_RETURN_FAILURE) 
		      {
	       		 sprintf(errText,"Failed to stuff snippet %s %s %s for event %s\n",
   	        		   TraceReq[iTrReq].sta, TraceReq[iTrReq].chan, 
			              TraceReq[iTrReq].net,Snppt.eventId);
	       		 dbtsv_status( TypeError, ERR_STUFF_SNPT, errText ); 
			      }
				if (Debug_tr_sv ==1) 
   				     logit("et","Returned from ewdb_apps_putaway_NextSnippetForAnEvent: %d\n", rc);
		} /* if we have good data */

    }  /* end  if waitSec == 0 */

  }		/* end for(iTrReq < nTrReq) (Snippet Disposal) */

	/* Wait for stragglers
	 *********************/
	/* We now know how long till the last straggler arrives in the wave server. 
     Wait that long, and make a pass over the request structure array again, 
     collecting all stragglers. This time accept partial answers 
  ****************************************************************/
	if ( waitForWaves > TravelTimeout ) 
    waitForWaves=TravelTimeout;  /* don't wait longer than limit */
	if(Debug_tr_sv)
    logit("","Waiting for stragglers: %lf seconds\n", waitForWaves);
	sleep_ew( (long)(waitForWaves*1000) );

	/* Rebuild the menu
	 ******************/
	wsKillMenu( &MenuList );
	if (waitForWaves > 0) /* only if there are stragglers */
	{ 
    for (i=0;i< MAX_WAVESERVERS; i++)
    {
		  if ( wsIp[i][0] == 0 ) 
        break;
		  ret=wsAppendMenu(wsIp[i], wsPort[i], &MenuList, TimeoutSeconds*1000);
      if (ret != WS_ERR_NONE )
			{
      /* lots of possible errors, as from ws_clientII.h, but for now 
         we don't care.  if things went wrong, we dont deal with that server. 
			   Someday we may get fussier...*/
				sprintf(errText,"ora_trace_save: WARNING: Error %d!  Failed to get menu from %s:%s\n",
					    ret,wsIp[i],wsPort[i]);
        dbtsv_status( TypeError, ERR_WAVE_SERVER, errText );
			} /* if ! WS_ERR_NONE */

		  if (ret == WS_ERR_NO_CONNECTION) 
        logit("","ora_trace_save: could not get a connection to %s:%s\n",
              wsIp[i],wsPort[i]);
		  if (ret == WS_ERR_TIMEOUT) 
        logit("","ora_trace_save: timeout getting menu from %s:%s\n",
              wsIp[i],wsPort[i]);
		  if (ret == WS_ERR_BROKEN_CONNECTION) 
        logit("","ora_trace_save: connection to %s:%s broke during menu\n",
              wsIp[i],wsPort[i]);
    }  /* for i < MAX_WAVESERVERS */
  }  /* end if waitForWaves > 0 */

	/* loop over trace requests again, collecting stragglers 
	 *******************************************************/
	for (iTrReq=0; iTrReq<nTrReq; iTrReq++)
	{					/* begin loop over stragglers    (level 1) */
    	if (waitForWaves == 0) 
	      break;		/* there are no stragglers */

		if( TraceReq[iTrReq].waitSec > 0 )  /* then this one is a straggler */
		{
	      if(Debug_tr_sv)
   		     logit("","Asking for straggler %s %s %s\n", TraceReq[iTrReq].sta,
              TraceReq[iTrReq].chan,TraceReq[iTrReq].net);
		      /* We're desperate now: we'll accept partial data */
			TraceReq[iTrReq].partial =1;   

			/* Get it from wave server */
			rc = wsGetTraceBin( &(TraceReq[iTrReq]), &MenuList, TimeoutSeconds*1000 );  

			if (rc != WS_ERR_NONE ) 
      		{
		        logit("et"," ora_trace_save: after waiting, for trace %s %s %s: "
       		       	  "wave server client returned %d\n",
				      Snppt.sta, Snppt.chan, Snppt.net, rc); 
			  continue;
			}


			/* LUCKY DEBUG: CHECK for 0 length and 0 start time */
			if ((TraceReq[iTrReq].actLen == 0) || 
						(TraceReq[iTrReq].actStarttime == 0.0))
			{
				logit ("", "DEBUG: %s-%s-%s: Bad data: actLen=%d, actStarttime=%0.2f\n",
						TraceReq[iTrReq].sta, TraceReq[iTrReq].chan, TraceReq[iTrReq].net, 
						TraceReq[iTrReq].actLen, TraceReq[iTrReq].actStarttime);
			}
			else
			{

		      /* Set DBidChan to 0, so that it won't be taken as a valid idChan */
		      DBidChan = 0;

			/* Now call the Put Away snippet disposer
			 ****************************************/
		      rc = ewdb_apps_putaway_NextSnippetForAnEvent(DBidEvent, &(TraceReq[iTrReq]),
                                      &DBidWaveform,&DBidChan);
		      if (rc == EWDB_RETURN_FAILURE) 
		      {
	       		 sprintf(errText,"Failed to stuff straggler snippet %s %s %s for event %s\n",
   	        		   TraceReq[iTrReq].sta, TraceReq[iTrReq].chan, 
			              TraceReq[iTrReq].net,Snppt.eventId);
	       		 dbtsv_status( TypeError, ERR_STUFF_SNPT, errText ); 
			      }
			 	if (Debug_tr_sv ==1)  
			        logit("et","Returned from ewdb_apps_putaway_NextSnippetForAnEvent: %d\n", rc);

			}
		}


  }		/* end of loop collecting stragglers                    (level 1) */


  ewdb_apps_putaway_CloseSnippetEvent();		/* call the event closer */
  sleep_ew(500);
  goto topOfLoop;	/* do the next message */
}  /* end SnippetMaker() */

