/* 
 * ratHold.c --
 *
 *	Manages different holds of messages.
 *
 *
 * TkRat software and its included text is Copyright 1996-2002 by
 * Martin Forssén
 *
 * The full text of the legal notice is contained in the file called
 * COPYRIGHT, included with this distribution.
 */

#include <pwd.h>
#include <signal.h>
#include "rat.h"
#include <locale.h>


/*
 * Parts of the message and body handlers which are saved in the hold.
 */
static char *holdMessageParts[] = {"remail", "date", "from",
	"reply_to", "subject", "to", "cc", "bcc", "in_reply_to", "message_id",
	"save_to", "request_dsn", "role", "other_tags", (char *) NULL};
static char *holdBodyParts[] = {"type", "subtype", "encoding", "parameter",
	"id", "description", "charset", "filename", "removeFile", "pgp_sign",
	"pgp_encrypt", "disp_type", "disp_parm", (char *) NULL};

/*
 * Keeps track of number of held and deferred messages
 */
static int numHeld, numDeferred;

/*
 * Save bodyparts during hold
 */
static int RatHoldBody(Tcl_Interp *interp, FILE *fPtr, char *baseName,
	char *handler, char **listValue, int *listValueSize, int intId);

/*
 *----------------------------------------------------------------------
 *
 * RatHold --
 *
 *	See ../doc/interface
 *
 * Results:
 *      A standard tcl result.
 *
 * Side effects:
 *      None.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatHold(ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
{
    static Tcl_Obj *fileListPtr = NULL;
    char buf[1024];
    const char *holdDir;
    Tcl_Obj *oPtr;
    int index;

    if (objc < 2) goto usage;
    if (NULL == (holdDir = RatGetPathOption(interp, "hold_dir"))
	|| (0 != mkdir(holdDir, DIRMODE) && EEXIST != errno)) {
	Tcl_AppendResult(interp, "error creating directory \"", holdDir,
		"\": ", Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }

    if (!strcmp(Tcl_GetString(objv[1]), "insert")) {
	if (objc != 4) goto usage;
	return RatHoldInsert(interp, holdDir, Tcl_GetString(objv[2]),
			     Tcl_GetString(objv[3]));

    } else if (!strcmp(Tcl_GetString(objv[1]), "list")) {

	if (fileListPtr) {
	    Tcl_DecrRefCount(fileListPtr);
	}
	fileListPtr = Tcl_NewObj();
	return RatHoldList(interp, holdDir, fileListPtr);

    } else if (!strcmp(Tcl_GetString(objv[1]), "extract")) {
	if (objc != 3
	    || TCL_OK != Tcl_GetIntFromObj(interp, objv[2], &index)) {
	    goto usage;
	} else if (!fileListPtr) {
	    Tcl_SetResult(interp,"You must list the content of the hold first",
			  TCL_STATIC);
	    return TCL_ERROR;
	} else {
	    Tcl_ListObjIndex(interp, fileListPtr, index, &oPtr);
	    snprintf(buf, sizeof(buf), "%s/%s", holdDir, Tcl_GetString(oPtr));
	    return RatHoldExtract(interp, buf, NULL, NULL);
	}
    }

 usage:
    Tcl_AppendResult(interp, "Usage error of \"", Tcl_GetString(objv[0]), "\"",
		     (char *) NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * RatHoldBody --
 *
 *	Saves a bodypart into the file specified by fPtr. The handler of
 *	the bodypart is passed in the handler argument. We also get a
 *	listValue buffer and the length of it which we may use when
 *	converting list elements. The last argument is an internal counter
 *	which use is explained below. The baseName argument is a string
 *	which contains the first part of the name of any new files. The
 *	new value of intId is returned.
 *
 *	The goal of this function is to generate code which recreates
 *	the structure we are passed, but with another set of handlers.
 *	The handlers are on the form holdX, where X is a number. To get
 *	unique X'es we use the holdId variable which is incremented
 *	every time we need a new handler. When we enter this routine
 *	holdId has a good value. The problem is that this bodypart
 *	must include references to all its children, and we don't know
 *	which id's the children are going to get (they may create their
 *	own children). This is solved by storing the name of the reference
 *	variable in another variable holdRefX where the number X is taken
 *	from the intId argument. Then we can add code so that each child
 *	adds itself to the reference list.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      The size of the space allocated in listValue may be increased,
 *	this is mirrored in the listValueSize argument.
 *
 *
 *----------------------------------------------------------------------
 */

static int
RatHoldBody(Tcl_Interp *interp, FILE *fPtr, char *baseName, char *handler,
	    char **listValue, int *listValueSize, int intId)
{
    int i, newSize, flags, objc;
    const char *value;
    Tcl_Obj *vPtr, **objv;

    /*
     * Write standard part
     */
    fprintf(fPtr, "global hold${holdId}\n");
    for (i=0; holdBodyParts[i]; i++) {
	if ((value = Tcl_GetVar2(interp, handler, holdBodyParts[i],
		TCL_GLOBAL_ONLY))) {
	    if ((newSize = Tcl_ScanElement(value, &flags)) > *listValueSize) {
		*listValueSize = newSize+1;
		*listValue = (char*) ckrealloc(*listValue, *listValueSize);
	    }
	    Tcl_ConvertElement(value, *listValue, flags);
	    fprintf(fPtr, "set hold${holdId}(%s) %s\n", holdBodyParts[i],
		    *listValue);
	}
    }
    /*
     * Handle children (if any)
     */
    if ((vPtr = Tcl_GetVar2Ex(interp, handler, "children", TCL_GLOBAL_ONLY))) {
	int myId = intId;

	Tcl_ListObjGetElements(interp, vPtr, &objc, &objv);
	fprintf(fPtr, "set holdRef%d hold${holdId}(children)\n", intId);
	fprintf(fPtr, "incr holdId\n");
	for (i=0; i<objc; i++) {
	    fprintf(fPtr, "lappend $holdRef%d hold${holdId}\n", myId);
	    intId = RatHoldBody(interp, fPtr, baseName, Tcl_GetString(objv[i]),
				listValue, listValueSize, intId+1);
	    if (0 > intId) {
		return -1;
	    }
	}
	fprintf(fPtr, "unset holdRef%d\n", myId);
    } else {
	fprintf(fPtr, "incr holdId\n");
    }

    return intId;
}

/*
 *----------------------------------------------------------------------
 *
 * RatHoldInsert --
 *
 *	Inserts a message into the specified hold directory. The directory
 *	will be created if it doesn't exist.
 *
 * Results:
 *      A standard Tcl result. the result area will contain the name of the
 *	newly inserted entry (if everything went ok).
 *
 * Side effects:
 *	The hold on disk may obviously be modified.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatHoldInsert(Tcl_Interp *interp, const char *dir, char *handler,
	      const char *description)
{
    int listValueSize = 0, newSize, flags, i, result = 0;
    char baseName[1024], buf[1024];
    char *listValue = NULL;
    struct stat sbuf;
    const char *value;
    FILE *fPtr;

    i = 0;
    do {
	snprintf(baseName, sizeof(baseName), "%s/%s_%x_%xM",
		dir, Tcl_GetHostName(), (unsigned int)getpid(), i++);
    } while(!stat(baseName, &sbuf));

    /*
     * Write description file
     */
    snprintf(buf, sizeof(buf), "%s.desc", baseName);
    if (NULL == (fPtr = fopen(buf, "w"))) {
	Tcl_AppendResult(interp, "error creating file \"", buf,
		"\": ", Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }
    (void)fprintf(fPtr, "%s\n", description);
    (void)fclose(fPtr);

    /*
     * Write main file
     */
    if (NULL == (fPtr = fopen(baseName, "w"))) {
	Tcl_AppendResult(interp, "error creating file \"", baseName,
		"\": ", Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }
    fprintf(fPtr, "global hold${holdId}\n");
    for (i=0; holdMessageParts[i]; i++) {
	if ((value = Tcl_GetVar2(interp, handler, holdMessageParts[i],
		TCL_GLOBAL_ONLY))) {
	    if ((newSize = Tcl_ScanElement(value, &flags)) > listValueSize){
		listValueSize = newSize+1;
		listValue = (char*) ckrealloc(listValue, listValueSize);
	    }
	    Tcl_ConvertElement(value, listValue, flags);
	    fprintf(fPtr, "set hold${holdId}(%s) %s\n", holdMessageParts[i],
		    listValue);
	}
    }

    snprintf(buf, sizeof(buf), "%s tag ranges noWrap",
	    Tcl_GetVar2(interp, handler, "composeBody", TCL_GLOBAL_ONLY));
    Tcl_Eval(interp, buf);
    if ((newSize = Tcl_ScanElement(Tcl_GetStringResult(interp),
	    &flags)) > listValueSize){
	listValueSize = newSize+1;
	listValue = (char*) ckrealloc(listValue, listValueSize);
    }
    Tcl_ConvertElement(Tcl_GetStringResult(interp), listValue, flags);
    fprintf(fPtr, "set hold${holdId}(tag_range) %s\n", listValue);

    if ((value = Tcl_GetVar2(interp, handler, "body", TCL_GLOBAL_ONLY))) {
	fprintf(fPtr, "set hold${holdId}(body) hold[incr holdId]\n");
	result = RatHoldBody(interp, fPtr, baseName, (char*)value, &listValue, 
			     &listValueSize, 0);
    }
    ckfree(listValue);
    if ( 0 > fprintf(fPtr, "\n") || 0 != fclose(fPtr) || 0 > result) {
	struct dirent *direntPtr;
	DIR *dirPtr;
	char *cPtr;

	(void)fclose(fPtr);
	for (cPtr = baseName+strlen(baseName)-1; *cPtr != '/'; cPtr--);
	*cPtr++ = '\0';
	dirPtr = opendir(dir);
	while (0 != (direntPtr = readdir(dirPtr))) {
	    if (!strncmp(direntPtr->d_name, cPtr, strlen(cPtr))) {
		snprintf(buf, sizeof(buf),"%s/%s", baseName,direntPtr->d_name);
		(void)unlink(buf);
	    }
	}
	closedir(dirPtr);

	Tcl_AppendResult(interp, "error writing files: ",
		Tcl_PosixError(interp), (char *) NULL);
	return TCL_ERROR;
    }
    Tcl_SetResult(interp, baseName, TCL_VOLATILE);
    RatHoldUpdateVars(interp, dir, 1);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RatHoldList --
 *
 *	List the content of the specified hold directory.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *	The fileListPtr argument will be used to hold the return list
 *
 *
 *----------------------------------------------------------------------
 */

int
RatHoldList(Tcl_Interp *interp, const char *dir, Tcl_Obj *fileListPtr)
{
    struct dirent *direntPtr;
    char buf[1024];
    DIR *dirPtr;
    FILE *fPtr;
    Tcl_Obj *oPtr = Tcl_NewObj();
    int l;

    if (NULL == (dirPtr = opendir(dir))) {
	snprintf(buf, sizeof(buf), "Failed to open %s: %s",
		dir, Tcl_PosixError(interp));
	Tcl_SetResult(interp, buf, TCL_VOLATILE);
	return TCL_ERROR;
    }
    while (0 != (direntPtr = readdir(dirPtr))) {
	l = strlen(direntPtr->d_name);
	if (   'd' == direntPtr->d_name[l-4]
	    && 'e' == direntPtr->d_name[l-3]
	    && 's' == direntPtr->d_name[l-2]
	    && 'c' == direntPtr->d_name[l-1]) {
	    snprintf(buf, sizeof(buf), "%s/%s", dir, direntPtr->d_name);
	    fPtr = fopen(buf, "r");
	    fgets(buf, sizeof(buf), fPtr);
	    fclose(fPtr);
	    buf[strlen(buf)-1] = '\0';
	    Tcl_ListObjAppendElement(interp, oPtr, Tcl_NewStringObj(buf, -1));
	    snprintf(buf, sizeof(buf), direntPtr->d_name);
	    if (fileListPtr) {
		Tcl_ListObjAppendElement(interp, fileListPtr,
					 Tcl_NewStringObj(buf, strlen(buf)-5));
	    }
	}
    }
    closedir(dirPtr);
    Tcl_SetObjResult(interp, oPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RatHoldExtract --
 *
 *	Extract a specified held message.
 *
 * Results:
 *      A standard Tcl result. The handler of the new message will be left
 *	in the result area.
 *
 * Side effects:
 *	The content of holdId will be modified.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatHoldExtract(Tcl_Interp *interp, const char *prefix,
	       Tcl_Obj *usedArraysPtr, Tcl_Obj *filesPtr)
{
    static int holdId = 0;
    Tcl_Channel ch;
    char buf[1024], *cPtr;
    int i, oldId;
    Tcl_Obj *fPtr = Tcl_NewObj(), *oPtr;

    /*
     * We start by reading the file. This is complicated by the fact that
     * the file is encoded in utf-8.
     */
    if (NULL == (ch = Tcl_OpenFileChannel(interp, (char*)prefix, "r", 0))) {
	return TCL_ERROR;
    }
    Tcl_SetChannelOption(interp, ch, "-encoding", "utf-8");
    i = Tcl_Seek(ch, 0, SEEK_END);
    Tcl_Seek(ch, 0, SEEK_SET);
    Tcl_ReadChars(ch, fPtr, i, 0);
    Tcl_Close(interp, ch);

    /*
     * Now we should eval the data, first we set the holdId variable
     * so that the file can generate unique handlers, after the read
     * we remember a new value of holdId. Then we get the right entry
     * in the list of files.
     */
    oldId = holdId;
    sprintf(buf, "%d", holdId);
    Tcl_SetVar(interp, "holdId", buf, 0);
    Tcl_IncrRefCount(fPtr);
    Tcl_EvalObjEx(interp, fPtr, TCL_EVAL_DIRECT);
    Tcl_DecrRefCount(fPtr);
    sprintf(buf, "hold%d", holdId);
    if (NULL == Tcl_GetVar2Ex(interp, buf, "role", 0)) {
	oPtr = Tcl_GetVar2Ex(interp, "option", "default_role",TCL_GLOBAL_ONLY);
	Tcl_SetVar2Ex(interp, buf, "role", oPtr, 0);
    }
    Tcl_SetResult(interp, buf, TCL_VOLATILE);
    oPtr = Tcl_GetVar2Ex(interp, "holdId", NULL, 0);
    Tcl_GetIntFromObj(interp, oPtr, &holdId);
    if (usedArraysPtr) {
	for (i=oldId; i<holdId; i++) {
	    sprintf(buf, "hold%d", i);
	    Tcl_ListObjAppendElement(interp, usedArraysPtr,
				     Tcl_NewStringObj(buf, -1));
	}
    }
    snprintf(buf, sizeof(buf), "%s.desc", prefix);
    if (filesPtr) {
	Tcl_ListObjAppendElement(interp, filesPtr,Tcl_NewStringObj(prefix,-1));
	Tcl_ListObjAppendElement(interp, filesPtr, Tcl_NewStringObj(buf, -1));
    } else {
	unlink(prefix);
	unlink(buf);
    }
    fflush(stderr);

    strlcpy(buf, prefix, sizeof(buf));
    if ((cPtr = strrchr(buf, '/'))) *cPtr = '\0';
    RatHoldUpdateVars(interp, buf, -1);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RatHoldInitVars --
 *
 *	Initialize some hold variables
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	Some global variables are initialized
 *
 *----------------------------------------------------------------------
 */

void
RatHoldInitVars(Tcl_Interp *interp)
{
    const char *dir;

    if (NULL != (dir = RatGetPathOption(interp, "send_cache"))) {
	RatHoldList(interp, dir, NULL);
	Tcl_ListObjLength(interp, Tcl_GetObjResult(interp), &numDeferred);
    }

    numHeld = 0;
    if (NULL != (dir = RatGetPathOption(interp, "hold_dir"))) {
	if (TCL_OK == RatHoldList(interp, dir, NULL)) {
	    Tcl_ListObjLength(interp, Tcl_GetObjResult(interp), &numHeld);
	}
    }

    Tcl_SetVar2Ex(interp, "numDeferred", NULL, Tcl_NewIntObj(numDeferred),
	    TCL_GLOBAL_ONLY);
    Tcl_SetVar2Ex(interp, "numHeld", NULL, Tcl_NewIntObj(numHeld),
	    TCL_GLOBAL_ONLY);
}

/*
 *----------------------------------------------------------------------
 *
 * RatHoldUpdateVars --
 *
 *	Update the hold variables
 *
 * Results:
 *      None.
 *
 * Side effects:
 *	Some global variables may be modified
 *
 *----------------------------------------------------------------------
 */

void
RatHoldUpdateVars(Tcl_Interp *interp, const char *dir, int diff)
{
    const char *senddir, *varname;
    int *intvar;

    dir = cpystr(dir);
    senddir = RatGetPathOption(interp, "send_cache");
    if (senddir && !strcmp(dir, senddir)) {
	varname = "numDeferred";
	intvar = &numDeferred;
    } else {
	varname = "numHeld";
	intvar = &numHeld;
    }
    ckfree(dir);

    *intvar += diff;
    Tcl_SetVar2Ex(interp, (char*)varname, NULL, Tcl_NewIntObj(*intvar),
		  TCL_GLOBAL_ONLY);
}


syntax highlighted by Code2HTML, v. 0.9.1