/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
 */
/*
 * ShapeTools Version Control System
 *
 * save.c - main program for "save" command
 *
 * Authors: Axel.Mahler@cs.tu-berlin.de
 *	    Andreas.Lampen@cs.tu-berlin.de
 *
 * $Header: save.c[8.1] Thu Aug  4 16:17:10 1994 andy@cs.tu-berlin.de frozen $
 */

#include <ctype.h>
#include "atfs.h"
#include "atfstk.h"
#include "sttk.h"

#define SAVE_ATTRFILE "SVATTRS"

void saveAFile();

/*====================
 *  global variables
 *====================*/

					/* -?, -help */
EXPORT char *aliasName = NULL;		/* -a, -alias <alias> */
EXPORT int deltaFlag = AF_STORE_DELTA;	/* -delta, -nodelta */
EXPORT int forceFlag = FALSE;		/* -f, -force*/
EXPORT int fixType = 0;			/* -fix */
EXPORT char *fixString = NULL;
EXPORT int fixGen = AF_NOVNUM;
EXPORT int fixRev = AF_NOVNUM;
EXPORT time_t fixDate = 0;
EXPORT int newGenFlag = FALSE;		/* -g, -newgen */
EXPORT int ignBusyFlag = FALSE;         /* -ignore_busy_version */
EXPORT int keepLockFlag = FALSE;	/* -l, -lock */
EXPORT char *historyLog = NULL;		/* -m, -logmsg  <log message> */
EXPORT int vnumGen = AF_NOVNUM;		/* -n, -setvnum */
EXPORT int vnumRev = AF_NOVNUM;
					/* -q, -quiet */
EXPORT char *attrFile = NULL;		/* -setattrs <filename> */
EXPORT int stdinFlag = FALSE;		/* -stdin */

LOCAL  int nameCount;

/*========================
 *  command line parsing
 *========================*/

LOCAL int handleBinding (option, arg)
     char *option, *arg;
{
  int    bindType, bindGen, bindRev;
  char   *bindString;
  time_t bindDate;
  void   saveAFile ();

  if (!arg || !(*arg)) {
    sprintf (stMessage, "No binding specified -- '-%s' ignored.\n", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (1);
  }

  switch (option[0]) {
  case 'f': /* fix */
    fixType = atScanBinding (arg, &fixString, &fixGen, &fixRev, &fixDate);
    if (fixType == AT_BIND_DEFAULT) {
      sprintf (stMessage, "invalid binding -- '-%s' ignored.\n", option);
      stLog (stMessage, ST_LOG_MSGERR);
      return (1);
    }
    /* assume generation number, when fixString contains only digits  */
    if (fixType == AT_BIND_ALIAS) {
      int i=0, isGen = TRUE;
      while (fixString[i])
	if (!isdigit(fixString[i++]))
	  isGen = FALSE;
      if (isGen) {
	fixGen = atoi (fixString);
	fixType = AT_BIND_VNUM;
	fixString = NULL;
      }
    }
    break;
  case 'a': /* alias */
  aliasOpt:
    bindType = atScanBinding (arg, &aliasName, &bindGen, &bindRev, &bindDate);
    if (bindType != AT_BIND_ALIAS) {
      sprintf (stMessage, "invalid alias name -- '-%s' ignored.\n", option);
      stLog (stMessage, ST_LOG_MSGERR);
      return (1);
    }
    break;
  case 'n': /* number */
  numberOpt:
    bindType = atScanBinding (arg, &bindString, &vnumGen, &vnumRev, &bindDate);
    if (bindType != AT_BIND_VNUM) {
      sprintf (stMessage, "invalid version number -- '-%s' ignored.\n", option);
      stLog (stMessage, ST_LOG_MSGERR);
      return (1);
    }
    break;
  case 's': /* alias or number */
    if (option[3] == 'a')
      goto aliasOpt;
    else
      goto numberOpt;
  }
  return (0);
}

LOCAL int handleDelta (option, arg)
     char *option, *arg;
{
  switch (option[0]) {
  case 'n': /* nodelta */
    deltaFlag = AF_STORE_COMPLETE;
    break;
  case 'd': /* delta */
    deltaFlag = AF_STORE_DELTA;
    break;
  }
  return (0);
}

LOCAL int handleLog (option, arg)
     char *option, *arg;
{
  if (!arg || !(*arg)) {
    sprintf (stMessage, "No log message specified -- '-%s' ignored.\n", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (1);
  }
  historyLog = arg;
  return (0);
}

LOCAL int handleAttrFile (option, arg)
     char *option, *arg;
{
  if (!arg || !(*arg)) {
    sprintf (stMessage, "No attributes file specified -- '-%s' ignored.\n", option);
    stLog (stMessage, ST_LOG_MSGERR);
    return (1);
  }
  attrFile = arg;
  return (0);
}

LOCAL int printVersion ()
{
  char *saveversion();
  sprintf (stMessage, "This is %s version %s", stProgramName, saveversion());
  stLog (stMessage, ST_LOG_MSG);
  sprintf (stMessage, "using AtFStk version %s", atVersion());
  stLog (stMessage, ST_LOG_MSG);
  sprintf (stMessage, "        AtFS version %s", af_version());
  stLog (stMessage, ST_LOG_MSG);
  sprintf (stMessage, "    and Sttk version %s.", stVersion());
  stLog (stMessage, ST_LOG_MSG);
  stExit (0);
  return (0);
}

static int usage();

static StOptDesc optDesc[] = {
  { "?",	PCALL,		usage,		NULL,			NULL },
  { "a",	PARG,		handleBinding,	NULL,			NULL },
  { "alias",	PARG,		handleBinding,	NULL,			NULL },
  { "delta",	PCALL,		handleDelta,	NULL,			NULL },
  { "f",	PSWITCH|PSET,	NULL,		&forceFlag,		NULL },
  { "force",	PSWITCH|PSET,	NULL,		&forceFlag,		NULL },
  { "fix",	PARG,		handleBinding,	NULL,			NULL },
  { "g",	PSWITCH|PSET,	NULL,		&newGenFlag,		NULL },
  { "help",	PCALL,		usage,		NULL,			NULL },
  { "ignore_busy_version", PSWITCH|PSET, NULL,  &ignBusyFlag,           NULL },
  { "l",	PSWITCH|PSET,	NULL,		&keepLockFlag,		NULL },
  { "lock",	PSWITCH|PSET,	NULL,		&keepLockFlag,		NULL },
  { "logmsg",	PARG,		handleLog,	NULL,			NULL },
  { "m",	PARG,		handleLog,	NULL,			NULL },
  { "n",	PARG,		handleBinding,	NULL,			NULL },
  { "newgen",	PSWITCH|PSET,	NULL,		&newGenFlag,		NULL },
  { "nodelta",	PCALL,		handleDelta,	NULL,			NULL },
  { "q",	PSWITCH|PSET,	NULL,		&stQuietFlag,		NULL },
  { "quiet",	PSWITCH|PSET,	NULL,		&stQuietFlag,		NULL },
  { "setattrs",	PARG,		handleAttrFile,	NULL,			NULL },
  { "setvnum",	PARG,		handleBinding,	NULL,			NULL },
  { "stdin",	PSWITCH|PSET,	NULL,		&stdinFlag,		NULL },
  { "version",	PSWITCH,	printVersion,	NULL,			NULL },
  { NULL, 	0, 		NULL, 		NULL, 			NULL },
};

LOCAL int usage()
{
  stLog ("Usage:", ST_LOG_MSGERR);
  stShortUsage (stProgramName, optDesc, "names...");
  atBindUsage ("");
  stExit (1);
  return (1);
}

/*=====================
 *  signal handlers
 *=====================*/

LOCAL Sigret_t interrupt_action ()
     /* is executed by appropriate signal handler */
{
  disableSig (SIGINT);
  stRmRegisteredFiles ();
  if ((nameCount - stThisTransaction.tr_seqno) > 1) { 
    sprintf (stMessage, "\ncompletely stop saving (%d files unsaved) ?",
	     nameCount - stThisTransaction.tr_seqno);
    if (stAskConfirm (stMessage, "no")) {
      if (stThisTransaction.tr_done) {
	sprintf (stMessage, "\ntoo late, %s already saved", stThisTransaction.tr_fname);
	stLog (stMessage, ST_LOG_MSG);
	enableSig();
	return; /* continue where we've been interrupted */
      }
      sprintf (stMessage, "%s not saved", stThisTransaction.tr_fname);
      stLog (stMessage, ST_LOG_MSG);
      enableSig();
      stCatchSigs (); /* mostly to deal with %^%$#$ Sys V signal-handling */
      af_cleanup ();
      longjmp (stThisTransaction.tr_env, 1);
    }
    else {
      sprintf (stMessage, "%s not saved", stThisTransaction.tr_fname);
      stLog (stMessage, ST_LOG_MSG);
      stExit (1);
    }
  }
  else {
    sprintf (stMessage, "\n%s %ssaved", stThisTransaction.tr_fname,
	     stThisTransaction.tr_done ? "already " : "not ");
    stLog (stMessage, ST_LOG_MSG);
    stExit (1);
  }
}

/*====================
 *  main program
 *====================*/

EXPORT int main (argc, argv)
     int argc; char **argv;
{
  int  tmpArgc, newArgc, i, j, k, retCode=0;
  char *cp, **tmpArgv, **newArgv, path[PATH_MAX], *bindPtr;
  Af_key curKey;
  Af_set *setPtr;

  stProgramName = (cp = strrchr (argv[0], '/')) ? ++cp : argv[0];

  if (argc < 2) {
    usage ();
    stExit (1);
  }

  if (isupper (stProgramName[0]))
    newGenFlag = TRUE;

  if (atBindOptions (argc, argv, &tmpArgc, &tmpArgv))
    stExit (1);

  if (stParseArgs (tmpArgc, tmpArgv, &newArgc, &newArgv, optDesc))
    stExit (1);

  if (newArgc == 1) {
    stLog ("No name(s) given.", ST_LOG_MSGERR);
    stExit (1);
  }

  if (ignBusyFlag && !newGenFlag) {
    stLog ("Option \"-ignore_busy_version\" makes no sense here. Ignored.",
	   ST_LOG_MSGERR);
  }

  if (fixType && newGenFlag) {
    stLog ("Incompatible options: '-fix' and '-newgen'.", ST_LOG_MSGERR);
    stExit (1);
  }
  
  stInterruptAction = interrupt_action;
  stCatchSigs ();

  if (!attrFile)
    attrFile = getenv (SAVE_ATTRFILE);

  nameCount = newArgc-1;

  k=0;
  stThisTransaction.tr_rc = 0;
  for (i = 1; i < newArgc; i++) {
    /* interpret version binding in brackets as
       version number or alias to be set */
    if ((bindPtr = strchr (newArgv[i], '['))) {
      int bindType, gen, rev;
      char *alias;
      time_t dummy;
      bindType = atScanBinding (bindPtr, &alias, &gen, &rev, &dummy);
      if (bindType == AT_BIND_VNUM) {
	vnumGen = gen;
	vnumRev = rev;
      }
      else if (bindType == AT_BIND_ALIAS) {
	aliasName = alias;
      }
      else {
	sprintf (stMessage, "invalid version number or alias -- '-%s' ignored.\n", bindPtr);
	stLog (stMessage, ST_LOG_MSGERR);
      }
      *bindPtr = '\0';
    }
    if ((setPtr = atBindSet (newArgv[i], NULL, ignBusyFlag ? 
			     AT_BIND_LASTSAVED : AT_BIND_LAST)) == NULL) {
      stLog (atBindErrorMsg, ST_LOG_ERROR);
      stExit (2);
    }
    if (af_nrofkeys (setPtr) == 0) {
      sprintf (stMessage, "%s: nothing found -- skipped.", newArgv[i]);
      stLog (stMessage, ST_LOG_MSGERR);
      retCode++;
      continue;
    }
    strcpy (path, af_afpath (newArgv[i]));
    for (j = 0; j < af_nrofkeys (setPtr); j++) {
      af_setgkey (setPtr, j, &curKey);
      if (!setjmp (stThisTransaction.tr_env)) {
	stThisTransaction.tr_seqno = k++;
	strcpy (stThisTransaction.tr_fname, af_retattr (&curKey, AF_ATTUNIXNAME));
	stThisTransaction.tr_done = FALSE;
	af_transaction ();
	saveAFile (path, &curKey);
	af_dropkey (&curKey);
        if (af_commit () < 0) {
	  sprintf (stMessage, "Cannot save changes for %s%s%s -- %s.", path, path[0] ? "/" : "",
		   af_retattr (&curKey, AF_ATTUNIXNAME), af_errmsg ("af_commit"));
	  stLog (stMessage, ST_LOG_ERROR);
	  retCode++;
	}
      }
      else { /* stThisTransaction was aborted */
	retCode += stThisTransaction.tr_rc;
	af_abort ();
      }
    }
  }
  if (nameCount > 1)
    stLog ("done.", ST_LOG_MSG);
  return (retCode);
}

