/* 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
 *
 *	retrv.c - main program for "retrv" command
 *
 *      by Axel.Mahler@cs.tu-berlin.de
 *	and Andreas.Lampen@cs.tu-berlin.de
 *
 *	$Header: retrv.c[6.0] Tue Jun 29 16:45:32 1993 andy@cs.tu-berlin.de frozen $
 */

/*
 *  Retrieve a previously saved version of a file. We orient us pretty much
 *  towards the check-out operation of RCS. Retrieve determines the
 *  archive name to be searched from the given file names. Unless otherwise
 *  specified by the project context (-p), the archive is expected to
 *  reside in the AtFS subdirectory. The retrieved version will be created
 *  in the current directory. Retrieve tries to be careful if an
 *  attempt is made to overwrite an existing busy-version: unless -f
 *  is specified, retrv will ask the caller for permission.
 *  If no busy version exists, one is created with the modes of the
 *  formerly saved version. If one exists, it's modes are kept unless
 *  -m is given.
 *  There's a number of ways to specify which version should be retrieved.
 *  With -V an explicit version can be selected. Another argument to -V
 *  could be a symbolic name (hopefully unique). Alternatively, versions
 *  can be selected by supplying certain attribute values to retrv, such as
 *  the name of the author, the version state, a generation number or a 
 *  set of user defined attributes, possibly describing a variant. In case
 *  that more than one version fits the desired attributes, the newest
 *  of them is selected, unless -x (exact!) is specified. -V implies -x.
 */

#include "atfs.h"
#include "atfstk.h"
#include "sttk.h"

void showAFile(), retrieveAFile();

/*====================
 *  global variables
 *====================*/
					/* -?, -help	*/
EXPORT int copyFlag = FALSE;		/* -c, -copy	(retrv) */
EXPORT char *destPath = NULL;		/* -dest	(retrv) */
EXPORT int fixFlag = FALSE;		/* -fix		(retrv) */
EXPORT int forceFlag = FALSE;		/* -f, -force	(retrv) */
EXPORT char *intent = NULL;		/* -i, -intent	(retrv) */
EXPORT int lockFlag = FALSE;		/* -l, -lock	(retrv) */
					/* -q, -quiet	*/
EXPORT int stdinFlag = FALSE;		/* -stdin	(retrv)  */
					/* -version	*/
EXPORT int expandFlag = TRUE;		/* -xpoff	*/
EXPORT int vcatFlag = FALSE;
EXPORT Af_key *newLock = NULL;

LOCAL  int nameCount;

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

LOCAL int handleDestPath (option, arg)
     char *option, *arg;
{
  char *endPtr;
  struct stat iBuf;

  if (!arg || !(*arg)) {
    sprintf (stMessage, "No attributes file specified -- '%s' ignored.\n", option);
    stLog (stMessage, ST_LOG_ERROR);
    return (0);
  }
  if (destPath) {
    sprintf (stMessage, "Destination path already set to %s -- '%s' ignored.", arg, option);
    stLog (stMessage, ST_LOG_ERROR);
    return (0);
  }

  endPtr = &arg[strlen(arg) - 1];
  while (*endPtr == '/')
    *endPtr-- = '\0';

  if (stat (arg, &iBuf) < 0) {
    sprintf (stMessage, "Destination path %s does not exist -- '%s' ignored.", arg, option);
    stLog (stMessage, ST_LOG_ERROR);
    stExit (1);
  }
  if (!S_ISDIR(iBuf.st_mode)) {
    sprintf (stMessage, "Destination path %s is not a directory -- '%s' ignored.", arg, option);
    stLog (stMessage, ST_LOG_ERROR);
    stExit (1);
  }
  destPath = arg;
  return (0);
}

LOCAL int handleIntent (option, arg)
     char *option, *arg;
{
  if (!arg || !(*arg)) {
    sprintf (stMessage, "No intent message specified -- '%s' ignored.\n", option);
    stLog (stMessage, ST_LOG_ERROR);
    return (0);
  }
  if (!strcmp (arg, "-"))
    stdinFlag = TRUE;
  else
    intent = arg;
  return (0);
}

LOCAL int printVersion ()
{
  char *retrversion();
  sprintf (stMessage, "This is %s version %s", stProgramName, retrversion());
  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 },
  { "help",	PCALL,		usage,		NULL,			NULL },
  { "q",	PSWITCH|PSET,	NULL,		&stQuietFlag,		NULL },
  { "quiet",	PSWITCH|PSET,	NULL,		&stQuietFlag,		NULL },
  { "version",	PSWITCH,	printVersion,	NULL,			NULL },
  { "xpoff",	PSWITCH|PCLEAR,	NULL,		&expandFlag,		NULL },
  { NULL, 	0, 		NULL, 		NULL, 			NULL },
};

static StOptDesc retrvOptDesc[] = {
  { "c",	PSWITCH|PSET,	NULL,		&copyFlag,		NULL },
  { "copy",	PSWITCH|PSET,	NULL,		&copyFlag,		NULL },
  { "dest",	PARG,		handleDestPath,	NULL,			NULL },
  { "fix",	PSWITCH|PSET,	NULL,		&fixFlag,		NULL },
  { "f",	PSWITCH|PSET,	NULL,		&forceFlag,		NULL },
  { "force",	PSWITCH|PSET,	NULL,		&forceFlag,		NULL },
  { "i",	PARG,		handleIntent,	NULL,			NULL },
  { "intent",	PARG,		handleIntent,	NULL,			NULL },
  { "l",	PSWITCH|PSET,	NULL,		&lockFlag,		NULL },
  { "lock",	PSWITCH|PSET,	NULL,		&lockFlag,		NULL },
  { "q",	PSWITCH|PSET,	NULL,		&stQuietFlag,		NULL },
  { "stdin",	PSWITCH|PSET,	NULL,		&stdinFlag,		NULL },
  { NULL, 	0, 		NULL, 		NULL, 			NULL },
};

LOCAL int usage()
{
  stLog ("Usage:", ST_LOG_MSGERR);
  if (!strcmp (stProgramName, "vcat")) {
    stShortUsage (stProgramName, optDesc, "names...");
  }
  else {
    stShortUsage (stProgramName, retrvOptDesc, "");
    stShortUsage ("     ", optDesc, "names...");
  }
  atBindUsage ("");
  stExit (1);
  return (1);
}

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

LOCAL Sigret_t interrupt_action ()
     /* is executed by appropriate signal handler */
{
  disableSig (SIGINT);
  if (newLock && !(stThisTransaction.tr_done)) {
    /* Try to unlock an eventually placed new lock */
    atUnlock (newLock);
  }

  if (vcatFlag)
    stExit(1);

  if ((nameCount - stThisTransaction.tr_seqno) > 1) { 
    sprintf (stMessage, "\ncompletely stop retrieving (%d files pending) ?", 
	     nameCount - stThisTransaction.tr_seqno);
    if (stAskConfirm (stMessage, "no")) {
      if (stThisTransaction.tr_done) {
	sprintf (stMessage, "\ntoo late, %s already restored", stThisTransaction.tr_fname);
	stLog (stMessage, ST_LOG_MSG);
	enableSig();
	return; /* continue where we've been interrupted */
      }

      sprintf (stMessage, "%s not restored", stThisTransaction.tr_fname);
      stLog (stMessage, ST_LOG_MSG);
      enableSig ();
      stCatchSigs (); /* see comment in save.c */
      af_cleanup ();
      longjmp (stThisTransaction.tr_env, 1);
    }
    else {
      sprintf (stMessage, "%s not restored", stThisTransaction.tr_fname);
      stLog (stMessage, ST_LOG_MSG);
      stExit (1);
    }
  }
  else {
    sprintf (stMessage, "\n%s not restored", stThisTransaction.tr_fname);
    stLog (stMessage, ST_LOG_MSG);
    stExit (1);
  }
}

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

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

  if (!strcmp (stProgramName, "vcat"))
    vcatFlag = TRUE;

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

  atBindModeOption = 0;
  if (atBindOptions (argc, argv, &newArgc, &newArgv))
    stExit (1);

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

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

  if (vcatFlag) {
    newArgc = tmpArgc;
    newArgv = tmpArgv;
  }
  else {
    if (stParseArgs (tmpArgc, tmpArgv, &newArgc, &newArgv, retrvOptDesc))
      stExit (1);
  }

  if (lockFlag && destPath) {
    sprintf (stMessage, "No checkout with lock to distant directory %s.", destPath);
    stLog (stMessage, ST_LOG_ERROR);
    stExit (1);
  }

  if (fixFlag)
    lockFlag = TRUE;

  stInterruptAction = interrupt_action;
  stCatchSigs ();
  if (vcatFlag) {
    signal (SIGPIPE, interrupt_action);
  }

  nameCount = newArgc-1;
  k=0;
  stThisTransaction.tr_rc = 0;
  /* no arguments on the command line */
  if ((newArgc == 1) && vcatFlag)
    showAFile ("", NULL);
  for (i = 1; i < newArgc; i++) {
    /* do version binding */
    if (vcatFlag && !strcmp (newArgv[i], "-")) {
      /* This is a special case */
      showAFile ("", NULL);
      continue;
    }
    if (!atBindModeOption) {
      if (vcatFlag)
	atBindModeOption = AT_BIND_LAST; /* default for vcat */
      else
	atBindModeOption = AT_BIND_LASTSAVED; /* default for retrv */
    }
    if ((setPtr = atBindSet (newArgv[i], NULL, atBindModeOption)) == 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_ATTBOUND));
	stThisTransaction.tr_done = FALSE;
	newLock = NULL;
        af_transaction ();
	if (vcatFlag)
	  showAFile (path, &curKey);
	else
	  retrieveAFile (path, &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 ();
      }
      /* switch attribute expansion on */
      /* $__xpoff$ */
      tmpStr[0] = '\0';
      atExpandAttrs (&curKey, "$__xpon$", 8, tmpStr, 16, AT_EXPAND_STRING);
      /* xpon */
      af_dropkey (&curKey);
    }
  }
  if (nameCount > 1)
    stLog ("done.", ST_LOG_MSG);
  return (retCode);
}




