static char DmGuiApp_id[] = "$Header: DmGuiApp.cc,v 1.9 97/11/20 08:21:04 oreilly Exp $";


/*
$Log:	DmGuiApp.cc,v $
Revision 1.9  97/11/20  08:21:04  08:21:04  oreilly (Thomas C. O'Reilly)
*** empty log message ***

Revision 1.8  97/10/03  09:29:16  09:29:16  oreilly (Thomas C. O'Reilly)
*** empty log message ***

Revision 1.7  97/09/10  08:48:18  08:48:18  oreilly (Thomas C. O'Reilly)
Load and save preferences

Revision 1.6  97/08/12  14:37:27  14:37:27  oreilly (Thomas C. O'Reilly)
*** empty log message ***

Revision 1.5  97/04/30  19:21:16  19:21:16  oreilly (Thomas C. O'Reilly)
Check that DmGuiObject has been previously registering it when 
unregistering it.

Revision 1.4  97/03/20  12:29:08  12:29:08  oreilly (Thomas C. O'Reilly)
*** empty log message ***

Revision 1.3  97/03/17  21:15:37  21:15:37  oreilly (Thomas C. O'Reilly)
Added method executeCmd() for executing a shell command

Revision 1.2  96/10/28  09:11:57  09:11:57  oreilly (Thomas C. O'Reilly)
*** empty log message ***

Revision 1.1  96/07/22  10:41:53  10:41:53  oreilly (Thomas C. O'Reilly)
First external release

*/
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "DmGuiApp.h"
#include "stringUtils.h"

DmGuiApp::DmGuiApp(char *appClassName,
	       int *argc,
	       char **argv,
	       XrmOptionDescRec *optionList,
	       int sizeOfOptionList) 
  
  : VkApp(appClassName, argc, argv, optionList, sizeOfOptionList),
  dmGuiObjectList()
{
  _aperiodicHandler = NULL;
  setSignalHandlers();
  
  // Initialize Data Manager
  Errno ret;  
  if ((ret = dm_init(NULL)) != OK)
  {
    if (ret == EDM_NO_DAEMON)
    {
      fprintf(stderr, "Data Manager daemon isn't running\n");
    }
    else
    {
      fprintf(stderr, "Data Manager daemon initialization error %d\n", ret);
    }
    this->exit(1);
  }

  _aperiodicHandler = new AperiodicHandler();
  _periodicHandler = new PeriodicHandler();

  if ((fcntl(ConnectionNumber(theApplication->display()), F_SETFD, 1)) == -1)
  {
    fprintf(stderr, "DmGuiApp::DmGuiApp() - fcntl() failed\n");
    this->exit(1);
  }

  subPrograms = new SubPrograms("subPrograms");
}


DmGuiApp::~DmGuiApp()
{
}


void DmGuiApp::registerDmGuiObject(DmGuiObject *dmGuiObject)
{
  dmGuiObjectList.append(dmGuiObject);
}


int DmGuiApp::unregisterDmGuiObject(DmGuiObject *dmGuiObject)
{
  SListIter<DmGuiObject *> objects(dmGuiObjectList);
  
  objects.reset();
  DmGuiObject **obj;
  while (obj = objects.next())
  {
    if (*obj == dmGuiObject)
    {
      objects.remove();
      return 0;
    }
  }

  /* Couldn't find specified DmGuiObject */
  return -1;
}


void DmGuiApp::startTelemetryHandlers()
{
  _periodicHandler->start();
  _aperiodicHandler->start();
}


int DmGuiApp::registerConsumer(DmGuiObject *obj, int period)
{
  if (period == Aperiodic || period == AperiodicGuaranteed)
  {
    // Remove DmGuiObject from periodic handler if it's there
    _periodicHandler->unregisterObj(obj);

    // Register with aperiodic handler
    _aperiodicHandler->registerObj(obj);
  }
  else if (period <= 0)
  {
    // Bad period
    return -1;
  }
  else
  {
    // Remove DmGuiObject from aperiodic handler if it's there
    _aperiodicHandler->unregisterObj(obj);

    // Register with periodic handler
    _periodicHandler->registerObj(obj, period);  
  }
  return 0;
}


int DmGuiApp::unregisterConsumer(DmGuiObject *obj)
{
  if (_aperiodicHandler->unregisterObj(obj) == -1 &&
      _periodicHandler->unregisterObj(obj) == -1)
  {
    // Object not registered as consumer 
    return -1;
  }
  else
    return 0;
}


AperiodicHandler *DmGuiApp::aperiodicHandler()
{
  return _aperiodicHandler;
}



void DmGuiApp::exit(int status)
{
  MBool debug = FALSE;
  
  // Remove signal handlers
  clearSignalHandlers();

  if (_aperiodicHandler)
  {
    dprintf(stderr, "DmGuiApp::exit() - delete _aperiodicHandler\n");
    
    // Clean up semaphore and child process 
    delete _aperiodicHandler;
  }
  
  char errorBuf[errorMsgSize];
  if (savePreferences(errorBuf) == -1)
    fprintf(stderr, "%s\n", errorBuf);
  
  ::exit(status);
}




void DmGuiApp::signalHandler(int sigNo)
{
  static int signalNo;
  signalNo = sigNo;
  int status;
  MBool debug = FALSE;
  dprintf(stderr, "DmGuiApp::signalHandler()\n");
  pid_t pid;
  
  // Get pointer to this object, since we are in a static function  
  DmGuiApp *app = (DmGuiApp *)theApplication;

  Boolean abort = True; 

  if (sigNo == SIGCHLD) 
  {
    dprintf(stderr, "DmGuiApp::signalHandler() - reap child...\n");

    // Reap child
    pid = wait(&status);
    dprintf(stderr, "DmGuiApp::signalHandler() - pid=%d\n", pid);

    if (app->_aperiodicHandler && 
	pid == app->_aperiodicHandler->_monitorPid)
    {
      abort = True;
      app->_aperiodicHandler->_monitorPid = 0;
    }
    else
    {
      abort = False;
    }
    
    if (!abort)
    {
      // Non-critical child died, so keep going
      if (WIFEXITED(status))
      {
	fprintf(stderr, "Child %d; status=%d, exit code %d\n",
		pid, status, WEXITSTATUS(status));
      }
  
      // Reset signal handlers
      app->setSignalHandlers();

      app->subPrograms->exitProcess(pid, WEXITSTATUS(status));
      
      return;
    }
  }

  dprintf(stderr, "DmGuiApp::signalHandler() - got signal %d\n", 
	  signalNo);
  
  if (signalNo == SIGCHLD || signalNo == SIGINT || signalNo == SIGQUIT
      || signalNo == SIGTERM)
  {
    /* Do cleanup that absolutely must get done. Note that 
       followupSignalHandler() may not necessarily get called (e.g. if
       user hits two cntrl-C's while mouse is not in app's window)
    */
    if (app->_aperiodicHandler)
      app->_aperiodicHandler->cleanup();
  }
  
  XFlush(theApplication->display());
  
  XtAppAddWorkProc(theApplication->appContext(), 
		   &DmGuiApp::followupSignalHandler, 
		   &signalNo);
}



Boolean DmGuiApp::followupSignalHandler(XtPointer clientData)
{
  MBool debug = FALSE;
  int sigNo = *((int *)clientData);
  dprintf(stderr, 
	  "DmGuiApp::followupSignalHandler() for signal %d\n",
	  sigNo);

  MBool fatal = FALSE;
  
  fprintf(stderr, "Received signal %d\n", sigNo);

  if (sigNo == SIGCHLD)
  {
    fprintf(stderr, "subprocess died\n");
    fatal = TRUE;
  }
  else if (sigNo == SIGTERM || sigNo == SIGINT || sigNo == SIGQUIT)
    fatal = TRUE;


  if (fatal)
  {
    ((DmGuiApp *)theApplication)->exit(1);
  }

  
  return False;
}


void DmGuiApp::setSignalHandlers()
{
  signal(SIGCHLD, &DmGuiApp::signalHandler);
  signal(SIGTERM, &DmGuiApp::signalHandler);
  signal(SIGINT, &DmGuiApp::signalHandler);
  signal(SIGQUIT, &DmGuiApp::signalHandler);
}


void DmGuiApp::clearSignalHandlers()
{
  signal(SIGCHLD, SIG_IGN);
  signal(SIGTERM, SIG_IGN);
  signal(SIGINT, SIG_IGN);
  signal(SIGQUIT, SIG_IGN);
}
  


/* the following procedure is a copy of the implementation of 
 * system, modified to reset the handling of SIGINT, SIGQUIT, 
 * and SIGHUP before exec-ing */
int DmGuiApp::executeCmd(const char *cmd)
{
  Boolean debug = False;
  
  int status, pid, w;
  register void (*interruptSigHandler)(int);
  register void (*quitSigHandler)(int);
  register void (*childSigHandler)(int);

  if ((pid = vfork()) == 0) {
    // In child...
    signal(SIGINT, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);
    signal(SIGHUP, SIG_DFL);
    execl("/bin/sh", "sh", "-c", cmd, 0);
    perror("DmGuiApp::executeCmd()");
    _exit(127);
  }

  dprintf(stderr, "executeCmd() - child pid = %d\n", pid);
  
#ifdef ZILCH
  // Ignore signals until shell command is finished
  clearSignalHandlers();
  
  // Wait for shell command to finish
  dprintf(stderr, "executeCmd() - wait for shell command to finish\n");  
  while ((w = wait(&status)) != pid && w != -1)
  {
    dprintf(stderr, "executeCmd(): w = %d\n", w);
  }
  
  dprintf(stderr, "executeCmd() - shell command finished.\n");  
  dprintf(stderr, "executeCmd(): final w = %d\n", w); 
  if (w == -1)
  {
    perror("executeCmd()");
    status = -1;
  }
  
  // Restore signal handling
  setSignalHandlers();
  return status;
#endif

  return 0;
}


Boolean DmGuiApp::hasStickyConsumer(DM_Item dmItem)
{
  Boolean debug = False;

  SListIter<DmGuiObject *> objects(dmGuiObjectList);

  objects.reset();
  DmGuiObject **obj;
  
  while (obj = objects.next())
  {
    if ((*obj)->stickyConsumer())
    {
      if ((*obj)->consumes(dmItem))
      {
	dprintf(stderr, "Item consumed by sticky consumer \"%s\"\n",
		(*obj)->name());
	
        // Item is consumed by a sticky consumer
	return True;
      }
    }
  }

  return False;
}


void DmGuiApp::preferencesFile(char *filename)
{
  strcpy(filename, _preferencesFile);
  return;
}



int DmGuiApp::loadPreferences(char *errorBuf)
{
  FILE *fp;
  
  if ((fp = fopen((const char *)_preferencesFile, "r")) == NULL)
  {
    sprintf(errorBuf, "Couldn't open preferences file \"%s\"", 
	    _preferencesFile);

    return -1;
  }

  Boolean error = False;
  
  char buf[512];
  char buf2[512];

  DynArray<Preference *> preferences;
  
  while (fgets(buf, sizeof(buf), fp))
  {
    if (buf[strlen(buf)-1] == '\n')
      buf[strlen(buf)-1] = '\0';

    if (!strlen(buf))
      continue;
    
    strcpy(buf2, buf);

    char *keyword;
    if ((keyword = strtok(buf, ":")) == NULL)
    {
      sprintf(errorBuf, "Syntax error: \"%s\"\n", buf2);
      error = True;
      break;
    }

    trim(keyword);
    
    char *value;
    if ((value = strtok(NULL, "")) == NULL)
    {
      sprintf(errorBuf, "Missing value: \"%s\"\n", buf2);
      error = True;
      break;
    }

    trim(value);
    
    Preference *preference = new Preference(keyword, value);
    preferences.add(&preference);
  }
    
  fclose(fp);
  
  if (error)
    return -1;
  
  DmGuiObject **obj;

  SListIter<DmGuiObject *> objects(dmGuiObjectList);

  objects.reset();

  while (obj = objects.next())
  {
    if ((*obj)->loadPreferences(&preferences, errorBuf) == -1)
      fprintf(stderr, "%s\n", errorBuf);
  }  

  for (int i = 0; i < preferences.size(); i++)
  {
    Preference *pref;
    preferences.get(i, &pref);
    delete pref;
  }

  return 0;
}





int DmGuiApp::savePreferences(char *errorBuf)
{
  DynArray<Preference *> preferences;
  Preference *preference;

  /* Each DmGuiObject will write its preferences to file */

  DmGuiObject **obj;
  DmGuiApp *app = (DmGuiApp *)theApplication;

  SListIter<DmGuiObject *> objects(dmGuiObjectList);

  objects.reset();

  while (obj = objects.next())
  {
    if ((*obj)->savePreferences(&preferences, errorBuf) == -1)
      fprintf(stderr, "%s\n", errorBuf);
  }  

  FILE *fp;
  
  if ((fp = fopen((const char *)_preferencesFile, "w")) == NULL)
  {
    sprintf(errorBuf, "Couldn't open preferences file \"%s\"", 
	    _preferencesFile);
    
    return -1;
  }

  for (int i = 0; i < preferences.size(); i++)
  {
    Preference *pref;
    preferences.get(i, &pref);
    
    fprintf(fp, "%s : %s\n", 
	    pref->keyword(),
	    pref->value());
  }

  fclose(fp);
  
  for (int i = 0; i < preferences.size(); i++)
  {
    Preference *pref;
    preferences.get(i, &pref);
    
    delete pref;
  }

  return 0;
}

