/*
 * ratDisFolder.c --
 *
 *      This file contains code which implements disconnected folders.
 *
 * 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.
 */

/*
 *  * One directory per folder
 *  * In that directory we find the following files:
 *    master	- File containing information about master folder.
 *		  Contains the following entries one on each line:
 *			name
 *			folder_spec
 *    state	- State against master.
 *		  Contains the following entries one on each line:
 *			uidvalidity
 *			last known UID in master
 *    mappings	- File with mappings local_uid <> master_uid
 *    folder	- The local copy of the folder
 *    changes   - Changes which should be applied to the master once
 *		  we synchronize. This file should contain:
 *			delete uid		- message to delete
 *			flag UID flag value	- set flag to value
 *
 */

#include "ratFolder.h"
#include "ratStdFolder.h"
#include "mbx.h"

/*
 * The uid map
 */
typedef struct {
    unsigned long *map;
    unsigned long size;
    unsigned long allocated;
} RatUidMap;

/*
 * This is the private part of a disconnected folder info structure.
 */

typedef struct DisFolderInfo {
    char *dir;			/* Directory where local data is stored */
    Tcl_HashTable map;		/* Mappings local_uid > remote_uid */
    int mapChanged;		/* non null if mappings needs to be rewritten*/
    MAILSTREAM *master;		/* Mailstream, used only in online mode */
    int error;	                /* Error indicator variable */
    MAILSTREAM *local;		/* Mailstream of local folder */
    char *spec;	    	        /* Name of master folder */
    FolderHandlers handlers;	/* Event handlers */
    Tcl_Interp *interp;		/* Needed if event-handlers */
    RatFolderInfo *infoPtr;
    int exists, expunged;	/* Used by event handlers (indexes) */
    unsigned long lastUid;	/* Uid of last message in master folder */
    
    /* Original procs for local folder */
    RatInitProc *initProc;
    RatCloseProc *closeProc;
    RatUpdateProc *updateProc;
    RatInsertProc *insertProc;
    RatSetFlagProc *setFlagProc;
    RatGetFlagProc *getFlagProc;
    RatInfoProc *infoProc;
    RatSetInfoProc *setInfoProc;
    RatCreateProc *createProc;
} DisFolderInfo;

/*
 * Hashtable containing open disfolders
 * The dirname is the key and the infoPtr is the value
 */
Tcl_HashTable openDisFolders;

/*
 * Procedures private to this module.
 */
static RatInitProc Dis_InitProc;
static RatFinalProc Dis_FinalProc;
static RatCloseProc Dis_CloseProc;
static RatUpdateProc Dis_UpdateProc;
static RatInsertProc Dis_InsertProc;
static RatInfoProc Dis_InfoProc;
static RatSetFlagProc Dis_SetFlagProc;
static RatGetFlagProc Dis_GetFlagProc;
static RatCreateProc Dis_CreateProc;
static RatSetInfoProc Dis_SetInfoProc;
static RatSyncProc Dis_SyncProc;
static int CreateDir(char *dir);
static Tcl_ObjCmdProc RatSyncDisconnected;
static void Dis_FindAndSyncFolders(Tcl_Interp *interp, CONST84 char *dir);
static int Dis_SyncFolder(Tcl_Interp *interp, CONST84 char *dir, off_t size,
			  int force, MAILSTREAM **master);
static unsigned long DisDownloadMsgs(Tcl_Interp *interp,
				     MAILSTREAM *masterStream,
				     MAILSTREAM *localStream,
				     int *masterErrorPtr, CONST84 char *dir,
				     Tcl_HashTable *mapPtr,
				     FILE *mapFp, unsigned long startAfterUid,
				     unsigned long stopBeforeUid);
static unsigned long GetMasterUID(MAILSTREAM *s, Tcl_HashTable *mapPtr,
	int index);
static void UpdateFolderFlag(Tcl_Interp *interp, DisFolderInfo *disPtr,
	int index, RatFlag flag, int value);
static void ReadMappings(MAILSTREAM *s, const char *dir,Tcl_HashTable *mapPtr);
static void ReadOldMappings(MAILSTREAM *s, Tcl_HashTable *mapPtr, char *buf,
	int buflen, FILE *fp);
static RatUidMap *InitUidMap(MAILSTREAM *s);
static void FreeUidMap(RatUidMap *uidMap);
static unsigned long MsgNo(RatUidMap *uidMap, unsigned long uid);
static void CheckDeletion(RatFolderInfoPtr infoPtr, Tcl_Interp *interp);
static const char* PrepareDir(Tcl_Interp *interp, Tcl_Obj *defPtr);
static unsigned long DisUploadMsg(MAILSTREAM *masterStream,
				  MAILSTREAM *localStream,
				  const char *subject, const char *in_reply_to,
				  const char *message_id, char *envdate,
				  unsigned long local_uid,
				  Tcl_DString *message, char *date,
				  char *flags, FILE *mapFP,
				  Tcl_HashTable *mapPtr);
static HandleExists Dis_HandleExists;
static HandleExpunged Dis_HandleExpunged;
static void WriteMappings(DisFolderInfo *disPtr);


/*
 *----------------------------------------------------------------------
 *
 * RatDisFolderInit --
 *
 *      Initializes the disconnected folder command.
 *
 * Results:
 *      The return value is normally TCL_OK; if something goes wrong
 *	TCL_ERROR is returned and an error message will be left in
 *	the result area.
 *
 * Side effects:
 *	None.
 *
 *
 *----------------------------------------------------------------------
 */

int
RatDisFolderInit(Tcl_Interp *interp)
{
    Tcl_InitHashTable(&openDisFolders, TCL_STRING_KEYS);
    Tcl_CreateObjCommand(interp, "RatSyncDisconnected", RatSyncDisconnected,
	    NULL, NULL);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * RatDisFolderCreate --
 *
 *      Creates a disconnected folder entity.
 *
 * Results:
 *      The return value is normally TCL_OK; if something goes wrong
 *	TCL_ERROR is returned and an error message will be left in
 *	the result area.
 *
 * Side effects:
 *	A disconnected folder is created.
 *
 *
 *----------------------------------------------------------------------
 */

RatFolderInfo*
RatDisFolderCreate(Tcl_Interp *interp, Tcl_Obj *defPtr)
{
    const char *dir;
    Tcl_Obj **objv, *oPtr, *lPtr;
    RatFolderInfo *infoPtr;
    DisFolderInfo *disPtr;
    Tcl_HashEntry *entryPtr;
    int objc, unused, online;

    Tcl_ListObjGetElements(interp, defPtr, &objc, &objv);
    
    /*
     * Prepare directory
     */
    dir = PrepareDir(interp, defPtr);
    if (!dir) {
	return NULL;
    }
    disPtr = (DisFolderInfo *) ckalloc(sizeof(*disPtr));
    disPtr->dir = cpystr(dir);
    disPtr->spec = NULL;
    
    /*
     * Open filefolder
     */
    lPtr = Tcl_NewObj();
    Tcl_ListObjAppendElement(interp, lPtr, Tcl_NewStringObj("Base", 4));
    Tcl_ListObjAppendElement(interp, lPtr, Tcl_NewStringObj("file", 4));
    Tcl_ListObjAppendElement(interp, lPtr, Tcl_NewObj());
    oPtr = Tcl_NewStringObj(disPtr->dir, -1);
    Tcl_AppendToObj(oPtr, "/folder", 7);
    Tcl_ListObjAppendElement(interp, lPtr, oPtr);
    infoPtr = RatStdFolderCreate(interp, lPtr);
    if (NULL == infoPtr) {
	Tcl_DecrRefCount(lPtr);
	goto error;
    }
    Tcl_DecrRefCount(lPtr);

    /*
     * Read mappings
     */
    Tcl_InitHashTable(&disPtr->map, TCL_ONE_WORD_KEYS);
    ReadMappings(((StdFolderInfo*)infoPtr->private)->stream, 
	    disPtr->dir, &disPtr->map);

    infoPtr->name = Tcl_GetString(objv[3]);
    if (!*infoPtr->name) {
	infoPtr->name = "INBOX";
    }
    infoPtr->name = cpystr(infoPtr->name);
    infoPtr->type = "dis";
    infoPtr->private2 = (ClientData) disPtr;

    disPtr->master = NULL;
    disPtr->local = ((StdFolderInfo*)infoPtr->private)->stream;
    disPtr->lastUid = 0;
    disPtr->handlers.state = (void*)disPtr;
    disPtr->handlers.exists = Dis_HandleExists;
    disPtr->handlers.expunged = Dis_HandleExpunged;
    disPtr->interp = interp;
    disPtr->infoPtr = infoPtr;
    disPtr->initProc = infoPtr->initProc;
    disPtr->closeProc = infoPtr->closeProc;
    disPtr->updateProc = infoPtr->updateProc;
    disPtr->insertProc = infoPtr->insertProc;
    disPtr->setFlagProc = infoPtr->setFlagProc;
    disPtr->getFlagProc = infoPtr->getFlagProc;
    disPtr->infoProc = infoPtr->infoProc;
    disPtr->setInfoProc = infoPtr->setInfoProc;
    disPtr->createProc = infoPtr->createProc;

    infoPtr->initProc = Dis_InitProc;
    infoPtr->finalProc = NULL;
    infoPtr->closeProc = Dis_CloseProc;
    infoPtr->updateProc = Dis_UpdateProc;
    infoPtr->insertProc = Dis_InsertProc;
    infoPtr->setFlagProc = Dis_SetFlagProc;
    infoPtr->getFlagProc = Dis_GetFlagProc;
    infoPtr->infoProc = Dis_InfoProc;
    infoPtr->setInfoProc = Dis_SetInfoProc;
    infoPtr->createProc = Dis_CreateProc;
    infoPtr->syncProc = Dis_SyncProc;

    /*
     * Add to hash table
     */
    entryPtr = Tcl_CreateHashEntry(&openDisFolders, disPtr->dir, &unused);
    Tcl_SetHashValue(entryPtr, (ClientData)infoPtr);

    /*
     * Maybe go online?
     */
    oPtr = Tcl_GetVar2Ex(interp, "option", "online", TCL_GLOBAL_ONLY);
    Tcl_GetBooleanFromObj(interp, oPtr, &online);
    if (online) {
	infoPtr->finalProc = Dis_FinalProc;
    }
    
    return infoPtr;

error:
    ckfree(disPtr);
    return NULL;
}
/*
 *----------------------------------------------------------------------
 *
 * Dis_FinalProc --
 *
 *      Do final initialization if we are going online
 *
 * Results:
 *	None
 *
 * Side effects:
 *	May update the folder
 *
 *----------------------------------------------------------------------
 */
static void
Dis_FinalProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    char buf[1024];
    struct stat sbuf;

    snprintf(buf, sizeof(buf), "%s/master", disPtr->dir);
    stat(buf, &sbuf);
    Dis_SyncFolder(interp, disPtr->dir, sbuf.st_size, 1, &disPtr->master);
}

/*
 *----------------------------------------------------------------------
 *
 * RatDisPrepareDir --
 *
 *      Prepares the directory for a disconnected folder
 *
 * Results:
 *	A pointer to a static area holds the name of the directory or
 *	NULL on errors;
 *
 * Side effects:
 *	Updatesv the master-file
 *
 *----------------------------------------------------------------------
 */
static const char*
PrepareDir(Tcl_Interp *interp, Tcl_Obj *defPtr)
{
    const char *dir, *name;
    struct stat sbuf;
    Tcl_DString ds;
    FILE *fp;
    Tcl_Obj **objv, *lPtr;
    int objc;

    /*
     * Find directory and make sure it exists
     */
    if (!(dir = RatDisFolderDir(interp, defPtr))) {
	return NULL;
    }
    Tcl_ListObjGetElements(interp, defPtr, &objc, &objv);
    name = Tcl_GetString(objv[0]);
    if (!*name) {
	name = "INBOX";
    }

    /*
     * Initialize state-file and create folder-file if it does not exist
     */
    Tcl_DStringInit(&ds);
    Tcl_DStringAppend(&ds, dir, -1);
    Tcl_DStringAppend(&ds, "/state", 7);
    if (0 != stat(Tcl_DStringValue(&ds), &sbuf)) {
	fp = fopen(Tcl_DStringValue(&ds), "w");
	if (NULL == fp) {
	    Tcl_DStringFree(&ds);
	    return NULL;
	}
	fprintf(fp, "0\n0\n");
	fclose(fp);

	Tcl_DStringSetLength(&ds, strlen(dir));
	Tcl_DStringAppend(&ds, "/folder", 7);
	mbx_create(NIL, Tcl_DStringValue(&ds));
    }

    /*
     * Always update the master-file (the user may have changed some setting)
     */
    Tcl_DStringSetLength(&ds, strlen(dir));
    Tcl_DStringAppend(&ds, "/master", 7);
    fp = fopen(Tcl_DStringValue(&ds), "w");
    if (NULL == fp) {
	Tcl_DStringFree(&ds);
	return NULL;
    }
    lPtr = Tcl_NewObj();
    Tcl_ListObjAppendElement(interp, lPtr, Tcl_NewStringObj("Master", 6));
    Tcl_ListObjAppendElement(interp, lPtr, Tcl_NewStringObj("imap", 4));
    Tcl_ListObjAppendElement(interp, lPtr, Tcl_NewObj());
    Tcl_ListObjAppendElement(interp, lPtr, objv[3]);
    Tcl_ListObjAppendElement(interp, lPtr, objv[4]);
    fprintf(fp, "%s\n%s\n", name, RatGetFolderSpec(interp, lPtr));
    Tcl_DecrRefCount(lPtr);
    fclose(fp);

    Tcl_DStringFree(&ds);
    return dir;
}

/*
 *----------------------------------------------------------------------
 *
 * RatDisFolderOpenStream --
 *
 *      Opens the local part of a disconnected folder. This function may
 *	NOT be called while the folder is open.
 *
 * Results:
 *      A pointer to a MAILSTREAM or NULL on failures.
 *
 * Side effects:
 *	A disconnected folder is created.
 *
 *----------------------------------------------------------------------
 */

MAILSTREAM*
RatDisFolderOpenStream(Tcl_Interp *interp, Tcl_Obj *defPtr)
{
    static Tcl_DString ds;
    static int initialized = 0;
    const char *dir;
    MAILSTREAM *stream;

    if (initialized) {
	Tcl_DStringSetLength(&ds, 0);
    } else {
	Tcl_DStringInit(&ds);
	initialized = 1;
    }

    dir = PrepareDir(interp, defPtr);
    if (!dir) {
	return NULL;
    }

    /*
     * Open filefolder
     */
    Tcl_DStringAppend(&ds, dir, -1);
    Tcl_DStringAppend(&ds, "/folder", 7);
    stream = OpenStdFolder(interp, Tcl_DStringValue(&ds), NULL);
    return stream;
}


/*
 *----------------------------------------------------------------------
 *
 * Dis_InitProc --
 *
 *      See the documentation for initProc in ratFolder.h
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the documentation for initProc in ratFolder.h
 *
 *
 *----------------------------------------------------------------------
 */
static void
Dis_InitProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp, int index)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    /* Do nothing */
    (*disPtr->initProc)(infoPtr, interp, index);
}


/*
 *----------------------------------------------------------------------
 *
 * Dis_CloseProc --
 *
 *      See the documentation for closeProc in ratFolder.h
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the documentation for closeProc in ratFolder.h
 *
 *
 *----------------------------------------------------------------------
 */
static int
Dis_CloseProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp, int expunge)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    Tcl_HashEntry *entryPtr;
    int result;

    if (expunge) {
	CheckDeletion(infoPtr, interp);
    }
    result = (*disPtr->closeProc)(infoPtr, interp, expunge);
    entryPtr = Tcl_FindHashEntry(&openDisFolders, disPtr->dir);
    Tcl_DeleteHashEntry(entryPtr);
    WriteMappings(disPtr);
    Tcl_DeleteHashTable(&disPtr->map);
    ckfree(disPtr->dir);
    if (disPtr->master) {
	Std_StreamClose(interp, disPtr->master);
	disPtr->master = NULL;
    }
    ckfree(disPtr->spec);
    ckfree(disPtr);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * Dis_UpdateProc --
 *
 *      See the documentation for updateProc in ratFolder.h
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the documentation for updateProc in ratFolder.h
 *
 *
 *----------------------------------------------------------------------
 */
static int
Dis_UpdateProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp,RatUpdateType mode)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    int lastKnown;
    
    if (disPtr->master && 0 == disPtr->error) {
	disPtr->exists = lastKnown = infoPtr->number;
	disPtr->expunged = 0;
	if (RAT_SYNC == mode) {
	    mail_expunge(disPtr->master);
	} else {
	    mail_check(disPtr->master);
	}
	if (disPtr->exists != lastKnown-disPtr->expunged &&
	    0 == disPtr->error) {
	    MAILSTREAM *local =
		((StdFolderInfo*)disPtr->infoPtr->private)->stream;
	    char buf[1024];
	    FILE *fp;

	    /*
	     * Append new messages to local folder and uidmap
	     */
	    snprintf(buf, sizeof(buf), "%s/mappings", disPtr->dir);
	    fp = fopen(buf, "a");
	    if (NULL == fp) {
		return 0;
	    }
	    disPtr->lastUid = DisDownloadMsgs(disPtr->interp, disPtr->master,
					      local, &disPtr->error,
					      disPtr->dir, &disPtr->map, fp,
					      disPtr->lastUid, 0);
	    fclose(fp);
	}

	if (0 != disPtr->error) {
	    Std_StreamClose(interp, disPtr->master);
	    disPtr->master = NULL;
	    RatStdCheckNet(interp);
	}
    }
    if (RAT_SYNC == mode && 0 == disPtr->error) {
	CheckDeletion(infoPtr, interp);
	WriteMappings(disPtr);
    }

    return (*disPtr->updateProc)(infoPtr, interp, mode);
}

/*
 *----------------------------------------------------------------------
 *
 * Dis_InsertProc --
 *
 *      See the documentation for insertProc in ratFolder.h
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the documentation for insertProc in ratFolder.h
 *
 *
 *----------------------------------------------------------------------
 */
static int
Dis_InsertProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp, int argc,
	char *argv[])
{
    MAILSTREAM *local = ((StdFolderInfo*)infoPtr->private)->stream;
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    char flags[128], date[128], buf[1024];
    Tcl_Obj *subject, *ref, *msgid;
    unsigned long localUid, us, ue;
    MessageInfo *msgPtr;
    Tcl_CmdInfo cmdInfo;
    Tcl_DString ds;
    int i, ret;
    FILE *fp;

    localUid = local->uid_last;
    ret = (*disPtr->insertProc)(infoPtr, interp, argc, argv);

    if (disPtr->master && argc) {
	Tcl_DStringInit(&ds);
	snprintf(buf, sizeof(buf), "%s/mappings", disPtr->dir);
	fp = fopen(buf, "a");
	for (i=us=ue=0; i<argc; i++) {
	    Tcl_GetCommandInfo(interp, argv[i], &cmdInfo);
	    msgPtr = (MessageInfo*)cmdInfo.objClientData;
	    RatMessageGet(interp, msgPtr, &ds, flags, sizeof(flags),
			  date, sizeof(date));
	    RatPurgeFlags(flags, 0);
	    subject = RatMsgInfo(interp, msgPtr, RAT_FOLDER_SUBJECT);
	    ref = RatMsgInfo(interp, msgPtr, RAT_FOLDER_REF);
	    msgid = RatMsgInfo(interp, msgPtr, RAT_FOLDER_MSGID);
	    ue = DisUploadMsg(disPtr->master, local, Tcl_GetString(subject),
			      Tcl_GetString(ref), Tcl_GetString(msgid), NULL,
			      ++localUid, &ds, date, flags, fp, &disPtr->map);
	    if (0 == i) {
		us = ue;
	    }
	    disPtr->mapChanged = 1;
	    if (T != mail_ping(disPtr->master)) {
		disPtr->master = NULL;
		break;
	    }	
	    Tcl_DStringSetLength(&ds, 0);
	}
	Tcl_DStringFree(&ds);
	if (disPtr->lastUid+1 < us) {
            DisDownloadMsgs(interp, disPtr->master, local,
			    &disPtr->error, disPtr->dir,
			    &disPtr->map, fp, disPtr->lastUid+1, us);
        }
	fclose(fp);
	disPtr->lastUid = ue;
    }
    return ret;
}


/*
 *----------------------------------------------------------------------
 *
 * Dis_SetFlagProc --
 *
 *      See the documentation for setFlagProc in ratFolder.h
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the documentation for setFlagProc in ratFolder.h
 *
 *
 *----------------------------------------------------------------------
 */
static int
Dis_SetFlagProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp, int index,
	RatFlag flag, int value)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    FILE *fp = NULL;
    char buf[1024];
    unsigned long uid;

    uid = GetMasterUID(((StdFolderInfo*)infoPtr->private)->stream,
		       &disPtr->map, index);
    if (uid && disPtr->master) {
	snprintf(buf, sizeof(buf), "%ld", uid);
	if (value) {
	    mail_setflag_full(disPtr->master, buf, flag_name[flag].imap_name,
			      ST_UID);
	} else {
	    mail_clearflag_full(disPtr->master, buf, flag_name[flag].imap_name,
				ST_UID);
	}
    } else if (uid) {
	snprintf(buf, sizeof(buf), "%s/changes", disPtr->dir);
	if (NULL != (fp = fopen(buf, "a"))) {
	    fprintf(fp, "flag %ld %d %d\n", uid, flag, value);
	    fclose(fp);
	}
    }

    return (*disPtr->setFlagProc)(infoPtr, interp, index, flag, value);
}


/*
 *----------------------------------------------------------------------
 *
 * Dis_GetFlagProc --
 *
 *      See the documentation for getFlagProc in ratFolder.h
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the documentation for getFlagProc in ratFolder.h
 *
 *
 *----------------------------------------------------------------------
 */
static int
Dis_GetFlagProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp, int index,
	RatFlag flag)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    /* Do Nothing */
    return (*disPtr->getFlagProc)(infoPtr, interp, index,flag);
}


/*
 *----------------------------------------------------------------------
 *
 * Dis_InfoProc --
 *
 *      See the documentation for infoProc in ratFolder.h
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the documentation for infoProc in ratFolder.h
 *
 *
 *----------------------------------------------------------------------
 */

Tcl_Obj*
Dis_InfoProc(Tcl_Interp *interp, ClientData clientData,
	RatFolderInfoType type, int index)
{
    /* Do Nothing */
    return Std_InfoProc(interp, clientData, type, index);
}

/*
 *----------------------------------------------------------------------
 *
 * Dis_CreateProc --
 *
 *      See the documentation for createProc in ratFolder.h
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the documentation for createProc in ratFolder.h
 *
 *
 *----------------------------------------------------------------------
 */
static char*
Dis_CreateProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp, int index)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    /* Do Nothing */
    return (*disPtr->createProc)(infoPtr, interp, index);
}

/*
 *----------------------------------------------------------------------
 *
 * Dis_SetInfoProc --
 *
 *      Sets information about a message
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static void
Dis_SetInfoProc(Tcl_Interp *interp, ClientData clientData,
	RatFolderInfoType type, int index, Tcl_Obj *oPtr)
{
    /* Do Nothing */
    Std_SetInfoProc(interp, clientData, type, index, oPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateDir --
 *
 *      Checks that a given directory exists and creates it and any
 *	parent directories if they do not exist. This routine expects
 *	a complete path starting with '/'.
 *
 * Results:
 *	Non zero if failed and sets errno.
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static int
CreateDir(char *dir)
{
    struct stat sbuf;
    char *cPtr;

    /*
     * First we check if it already exists.
     */
    if (0 == stat(dir, &sbuf) && S_ISDIR(sbuf.st_mode)) {
	return 0;
    }

    /*
     * Go through all directories from the top and down and create those
     * which do not exist.
     */
    for (cPtr = strchr(dir+1, '/'); cPtr; cPtr = strchr(cPtr+1, '/')) {
	*cPtr = '\0';
	if (0 != stat(dir, &sbuf)) {
	    if (mkdir(dir, 0700)) {
		return 1;
	    }
	} else if (!S_ISDIR(sbuf.st_mode)) {
	    errno = ENOTDIR;
	    return 1;
	}
	*cPtr = '/';
    }
    if (0 != stat(dir, &sbuf)) {
	if (mkdir(dir, 0700)) {
	    return 1;
	}
    } else if (!S_ISDIR(sbuf.st_mode)) {
	errno = ENOTDIR;
	return 1;
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * RatSyncDisconnected --
 *
 *	Synchronizes all disconnected folders
 *
 * Results:
 *	None
 *
 * Side effects:
 *	All disconnected folders are updated
 *
 *
 *----------------------------------------------------------------------
 */
static int
RatSyncDisconnected(ClientData op, Tcl_Interp *interp, int objc,
		    Tcl_Obj *const objv[])
{
    CONST84 char *dirname;

    if (NULL == (dirname = RatGetPathOption(interp, "disconnected_dir"))) {
	return TCL_ERROR;
    }
    Dis_FindAndSyncFolders(interp, dirname);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Dis_FindAndSyncFolders --
 *
 *	Recurses over a directory tree and calls Dis_SyncFolder for each
 *	found folder.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	All disconnected folders are updated
 *
 *
 *----------------------------------------------------------------------
 */
static void
Dis_FindAndSyncFolders(Tcl_Interp *interp, CONST84 char *dir)
{
    struct stat sbuf;
    char buf[1024];
    DIR *dirPtr;
    struct dirent *direntPtr;

    /*
     * Check if this is a folder directory (contains a master-file)
     */
    strlcpy(buf, dir, sizeof(buf)-7);
    strlcat(buf, "/master", sizeof(buf));
    if (0 == stat(buf, &sbuf) && S_ISREG(sbuf.st_mode)) {
	Dis_SyncFolder(interp, dir, sbuf.st_size, 0, NULL);
	return;
    }

    /*
     * Otherwise check all entries and call Dis_FindAndSyncFolders for all
     * descendants
     */
    if (NULL == (dirPtr = opendir(dir))) {
	return;
    }
    while (dirPtr && 0 != (direntPtr = readdir(dirPtr))) {
	snprintf(buf, sizeof(buf), "%s/%s", dir, direntPtr->d_name);
	if (0 != stat(buf, &sbuf)
		|| !S_ISDIR(sbuf.st_mode)
		|| !strcmp(".", direntPtr->d_name)
		|| !strcmp("..", direntPtr->d_name)) {
	    continue;
	}
	Dis_FindAndSyncFolders(interp, buf);
    }
    closedir(dirPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * Dis_SyncFolder --
 *
 *	Synchronizes a specified folder.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static int
Dis_SyncFolder(Tcl_Interp *interp, CONST84 char *dir, off_t size, int force,
	       MAILSTREAM **master)
{
    char buf[1024], *name, *spec, *data, 
	    *header, *body, localMailbox[1024], datebuf[128], *cPtr;
    MESSAGECACHE *elt;
    unsigned long uid, msgno, lastUid, uidvalidity, len, nmsgs;
    MAILSTREAM *masterStream, *localStream;
    Tcl_HashTable *mapPtr;
    RatFolderInfoPtr infoPtr = NULL;
    DisFolderInfo *disPtr = NULL;
    Tcl_HashEntry *entryPtr;
    Tcl_HashSearch search;
    Tcl_CmdInfo cmdInfo;
    ENVELOPE *envPtr;
    int fd, i, *masterErrorPtr, error;
    FILE *fp = NULL;
    Tcl_DString ds;
    RatUidMap *uidMap = NULL;
    
    Tcl_DStringInit(&ds);

    /*
     * Read and parse masterfile & statefile
     */
    snprintf(buf, sizeof(buf), "%s/master", dir);
    if (-1 == (fd = open(buf, O_RDONLY))
	    || NULL == (data = (char*)ckalloc(size+1))
	    || size != read(fd, data, size)
	    || 0 != close(fd)) {
	RatLogF(interp, RAT_ERROR, "Failed to read masterfile", RATLOG_TIME);
	if (master) {
	    *master = NULL;
	}
	return TCL_ERROR;
    }
    name = data;
    if (NULL == (spec = strchr(data, '\n'))) {
	if (master) {
	    *master = NULL;
	}
	return TCL_ERROR;
    }
    *spec++ = '\0';
    if (NULL == (cPtr = strchr(spec, '\n'))) {
	if (master) {
	    *master = NULL;
	}
	return TCL_ERROR;
    }
    *cPtr = '\0';
    snprintf(buf, sizeof(buf), "%s/state", dir);
    if (NULL == (fp = fopen(buf, "r"))
	    || 2 != fscanf(fp, "%ld\n%ld", &uidvalidity, &lastUid)
	    || 0 != fclose(fp)) {
	RatLog(interp, RAT_ERROR, "Failed to read statefile", RATLOG_TIME);
	if (master) {
	    *master = NULL;
	}
	return TCL_ERROR;
    }

    if (!force && Tcl_GetCommandInfo(interp, "RatUP_NetsyncFolder", &cmdInfo)){
	Tcl_Obj *oPtr = Tcl_NewObj();
	int doSync = 0;

	Tcl_ListObjAppendElement(interp, oPtr,
		Tcl_NewStringObj("RatUP_NetsyncFolder", -1));
	Tcl_ListObjAppendElement(interp, oPtr, Tcl_NewStringObj(spec, -1));
	Tcl_IncrRefCount(oPtr);
	if (TCL_OK != Tcl_EvalObjEx(interp, oPtr, TCL_EVAL_GLOBAL)
		|| TCL_OK != Tcl_GetBooleanFromObj(interp,
			Tcl_GetObjResult(interp),&doSync)
		|| 0 == doSync) {
	    Tcl_DecrRefCount(oPtr);
	    ckfree(data);
	    if (master) {
		*master = NULL;
	    }
	    return TCL_ERROR;
	}
	Tcl_DecrRefCount(oPtr);
    }

    RatLogF(interp, RAT_INFO, "synchronizing", RATLOG_EXPLICIT, name);

    /*
     * Open connection
     * Check uidvalidity
     * Apply deletion commands and expunge
     * Apply flag commands
     * loop over messages in local folder
     *   Check if has master uid
     *   if YES check if still exists in master
     *     if NO delete
     *     if YES check flags and update from master
     *   if NO then append to master, should update with masters uid
     * Loop over new messages in master and append them to local
     */

    /*
     * Open connections
     */
    if (NULL != (entryPtr = Tcl_FindHashEntry(&openDisFolders, dir))) {
	infoPtr = Tcl_GetHashValue(entryPtr);
	strlcpy(localMailbox,
		   ((StdFolderInfo*)infoPtr->private)->stream->mailbox,
		   sizeof(localMailbox));
	disPtr = (DisFolderInfo*)infoPtr->private2;
	localStream = disPtr->local;
	masterStream = disPtr->master;
	masterErrorPtr = &disPtr->error;
	mapPtr = &disPtr->map;
	if (disPtr->lastUid) {
	    lastUid = disPtr->lastUid;
	}
    } else {
	snprintf(localMailbox, sizeof(localMailbox), "%s/folder", dir);
	masterStream = NIL;
	localStream = mail_open(NIL, localMailbox, NIL);
	mapPtr = (Tcl_HashTable*)ckalloc(sizeof(*mapPtr));
	Tcl_InitHashTable(mapPtr, TCL_ONE_WORD_KEYS);
	ReadMappings(localStream, dir, mapPtr);
	masterErrorPtr = &error;
    }
    if (!masterStream) {
	*masterErrorPtr = 0;
	masterStream = Std_StreamOpen(interp, spec, NIL, masterErrorPtr,
				      (disPtr ? &disPtr->handlers : NULL));
    }
    if (NULL == masterStream) {
	RatLog(interp, RAT_INFO, "", RATLOG_EXPLICIT);
	goto error;
    }
    if (disPtr && disPtr->spec) {
	spec = disPtr->spec;
    } else if (disPtr) {
	disPtr->spec = cpystr(spec);
    }

    if (!(0 == uidvalidity && 0 == lastUid)
	    && uidvalidity != masterStream->uid_validity) {
	RatLogF(interp, RAT_ERROR, "uidvalidity_changed", RATLOG_TIME);
	goto error;
    }

    /*
     * Apply deletion commands and expunge
     */
    RatLogF(interp, RAT_INFO, "uploading", RATLOG_EXPLICIT);
    snprintf(buf, sizeof(buf), "%s/changes", dir);
    if (NULL != (fp = fopen(buf, "r"))) {
	buf[sizeof(buf)-1] = '\0';
	while (fgets(buf, sizeof(buf)-1, fp), !feof(fp)) {
	    if (!strncmp(buf, "delete", 6)) {
		if (Tcl_DStringLength(&ds)) {
		    Tcl_DStringAppend(&ds, ",", 1);
		}
		sprintf(buf, "%ld", atol(buf+7));
		Tcl_DStringAppend(&ds, buf, -1);
	    }
	}
	if (Tcl_DStringLength(&ds)) {
	    mail_setflag_full(masterStream, Tcl_DStringValue(&ds),
		    flag_name[RAT_DELETED].imap_name, ST_UID);
	    mail_expunge(masterStream);
	}
	if (*masterErrorPtr) goto error;
    }

    /*
     * Build list of uids
     */
    uidMap = InitUidMap(masterStream);
    if (*masterErrorPtr) goto error;

    /*
     * Apply flag commands (and remove changes file)
     */
    if (NULL != fp) {
	RatFlag flag;
	int value;

	fseek(fp, 0, SEEK_SET);
	while (fgets(buf, sizeof(buf)-1, fp), !feof(fp)) {
	    if (!strncmp(buf, "flag", 4)) {
		sscanf(buf+5, "%ld %d %d", &uid, (int*)&flag, &value);
		sprintf(buf, "%ld", uid);
		if (0 == (msgno = MsgNo(uidMap, uid))) {
		    continue;
		}
		mail_fetchenvelope(masterStream, msgno);
		elt = mail_elt(masterStream, msgno);
		switch(flag) {
		case RAT_SEEN:
		    elt->seen = value;
		    break;
		case RAT_FLAGGED:
		    elt->flagged = value;
		    break;
		case RAT_DELETED:
		    elt->deleted = value;
		    break;
		case RAT_ANSWERED:
		    elt->answered = value;
		    break;
		case RAT_DRAFT:
		    elt->draft = value;
		    break;
		case RAT_RECENT:
		    break;
		}
		if (value) {
		    mail_setflag_full(masterStream, buf,
				      flag_name[flag].imap_name, ST_UID);
		} else {
		    mail_clearflag_full(masterStream, buf,
					flag_name[flag].imap_name, ST_UID);
		}
	    }
	}
	fclose(fp);
	if (*masterErrorPtr) goto error;
	snprintf(buf, sizeof(buf), "%s/changes", dir);
	unlink(buf);
    }

    /*
     * Download new messages
     */
    nmsgs = localStream->nmsgs;
    snprintf(buf, sizeof(buf), "%s/mappings", dir);
    fp = fopen(buf, "a");
    lastUid = DisDownloadMsgs(interp, masterStream, localStream,
			      masterErrorPtr, dir, mapPtr, fp, lastUid, 0);

    /*
     * Loop over messages and update
     */
    RatLogF(interp, RAT_INFO, "downloading_flags", RATLOG_EXPLICIT);
    for (i = 1; i <= nmsgs && !*masterErrorPtr; i++) {
	if ((uid = GetMasterUID(localStream, mapPtr, i-1))) {
	    msgno = MsgNo(uidMap, uid);
	    if (0 == msgno) {
		if (disPtr) {
		    UpdateFolderFlag(interp, disPtr, i, RAT_DELETED, 1);
		} else {
		    sprintf(buf, "%d", i);
		    mail_setflag(localStream, buf,
				 flag_name[RAT_DELETED].imap_name);
		}
		continue;
	    }
	    /*
	     * Update flags from master
	     */
	    envPtr = mail_fetchenvelope(masterStream, MsgNo(uidMap, uid));
	    elt = mail_elt(masterStream, MsgNo(uidMap, uid));
	    if (disPtr) {
		UpdateFolderFlag(interp,disPtr,i,RAT_SEEN,elt->seen);
		UpdateFolderFlag(interp,disPtr,i,RAT_DELETED,elt->deleted);
		UpdateFolderFlag(interp,disPtr,i,RAT_FLAGGED,elt->flagged);
		UpdateFolderFlag(interp,disPtr,i,RAT_ANSWERED,
			elt->answered);
		UpdateFolderFlag(interp,disPtr,i,RAT_DRAFT,elt->draft);
	    } else {
		MESSAGECACHE *lelt;

		lelt = mail_elt(localStream, i);
		sprintf(buf, "%d", i);
		if (elt->seen != lelt->seen) {
		    if (elt->seen) {
			mail_setflag(localStream, buf,
				     flag_name[RAT_SEEN].imap_name);
		    } else {
			mail_clearflag(localStream, buf,
				       flag_name[RAT_SEEN].imap_name);
		    }
		}
		if (elt->deleted != lelt->deleted) {
		    if (elt->deleted) {
			mail_setflag(localStream, buf,
				     flag_name[RAT_DELETED].imap_name);
		    } else {
			mail_clearflag(localStream, buf,
				       flag_name[RAT_DELETED].imap_name);
		    }
		}
		if (elt->flagged != lelt->flagged) {
		    if (elt->flagged) {
			mail_setflag(localStream, buf,
				     flag_name[RAT_FLAGGED].imap_name);
		    } else {
			mail_clearflag(localStream, buf,
				       flag_name[RAT_FLAGGED].imap_name);
		    }
		}
		if (elt->answered != lelt->answered) {
		    if (elt->answered) {
			mail_setflag(localStream, buf,
				     flag_name[RAT_ANSWERED].imap_name);
		    } else {
			mail_clearflag(localStream, buf,
				       flag_name[RAT_ANSWERED].imap_name);
		    }
		}
		if (elt->draft != lelt->draft) {
		    if (elt->draft) {
			mail_setflag(localStream, buf,
				     flag_name[RAT_DRAFT].imap_name);
		    } else {
			mail_clearflag(localStream, buf,
				       flag_name[RAT_DRAFT].imap_name);
		    }
		}
	    }
	} else {
	    /*
	     * Append the message to the master stream
	     */
	    Tcl_DStringSetLength(&ds, 0);
	    header = mail_fetchheader(localStream, i);
	    Tcl_DStringAppend(&ds, header, -1);
	    body = mail_fetchtext_full(localStream, i, &len, FT_PEEK);
	    Tcl_DStringAppend(&ds, body, len);
	    elt = mail_elt(localStream, i);
	    mail_date(datebuf, elt);
	    envPtr = mail_fetch_structure(localStream, i, NULL, 0);
	    lastUid = DisUploadMsg(masterStream, localStream, envPtr->subject,
				   envPtr->in_reply_to, envPtr->message_id,
				   envPtr->date, mail_uid(localStream, i), &ds,
				   datebuf, MsgFlags(elt), fp, mapPtr);
	    if (disPtr) {
		disPtr->mapChanged = 1;
	    }
	}
    }
    fclose(fp);

    /*
     * Update state file
     */
    snprintf(buf, sizeof(buf), "%s/state", dir);
    fp = fopen(buf, "w");
    fprintf(fp, "%ld\n%ld\n", masterStream->uid_validity, lastUid);
    fclose(fp);

    /*
     * Cleanup
     */
    if (!disPtr) {
	mail_close(localStream);
	for (entryPtr = Tcl_FirstHashEntry(mapPtr, &search); entryPtr;
		entryPtr = Tcl_NextHashEntry(&search)) {
	    ckfree(Tcl_GetHashValue(entryPtr));
	}
	Tcl_DeleteHashTable(mapPtr);
	ckfree(mapPtr);
	Std_StreamClose(interp, masterStream);
	masterStream = NULL;
    } else {
	Tcl_Obj *oPtr;
	int online;
	
	/*	
	 * If we are offline, then we should close the masterStream
	 */
	oPtr = Tcl_GetVar2Ex(interp, "option", "online", TCL_GLOBAL_ONLY);
	Tcl_GetBooleanFromObj(interp, oPtr, &online);
	if (!online) {
	    Std_StreamClose(interp, disPtr->master);
	    disPtr->master = NULL;
	}
    }
	
    RatLog(interp, RAT_INFO, "", RATLOG_EXPLICIT);
    ckfree(data);
    Tcl_DStringFree(&ds);

    /*
     * Trigger an update of the folder as well (if we are active)
     */
    if (disPtr) {
	disPtr->lastUid = lastUid;
	RatUpdateFolder(interp, disPtr->infoPtr, RAT_UPDATE);
    }
    FreeUidMap(uidMap);
    if (master) {
	*master = masterStream;
    }
    return TCL_OK;

error:
    RatLog(interp, RAT_INFO, "", RATLOG_EXPLICIT);
    if (!disPtr) {
	mail_close(localStream);
	for (entryPtr = Tcl_FirstHashEntry(mapPtr, &search); entryPtr;
		entryPtr = Tcl_NextHashEntry(&search)) {
	    ckfree(Tcl_GetHashValue(entryPtr));
	}
	Tcl_DeleteHashTable(mapPtr);
	ckfree(mapPtr);
    }
    if (masterStream) {
	Std_StreamClose(interp, masterStream);
	if (disPtr) {
	    disPtr->master = NULL;
	}
    }
    if (master) {
	*master = NULL;
    }
    if (uidMap) {
	FreeUidMap(uidMap);
    }
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * DisDownloadMsgs
 *
 *	Downloads a new message from the master folder to the local
 *	folder.
 *
 * Results:
 *	Last uid
 *
 * Side effects:
 *	The uidmap is updated (if one exists)
 *
 *
 *----------------------------------------------------------------------
 */

static unsigned long
DisDownloadMsgs(Tcl_Interp *interp, MAILSTREAM *masterStream,
		MAILSTREAM *localStream, int *masterErrorPtr,
		CONST84 char *dir, Tcl_HashTable *mapPtr, FILE *mapFp,
		unsigned long startAfterUid, unsigned long stopBeforeUid)
{

    ENVELOPE *envPtr;
    MESSAGECACHE *elt;
    unsigned long len, uid;
    char *body, *header, datebuf[128], statebuf[1024];
    Tcl_DString ds;
    STRING string;
    SEARCHPGM *pgm;
    int i;
    FILE *stateFp;

    if (0 == masterStream->nmsgs) {
	return masterStream->uid_last;
    }
    snprintf(statebuf, sizeof(statebuf), "%s/state", dir);

    pgm = mail_newsearchpgm();
    if (0 == stopBeforeUid) {
	stopBeforeUid = mail_uid(masterStream, masterStream->nmsgs)+1;
    }
    pgm->uid = mail_newsearchset();
    pgm->uid->first = startAfterUid+1;
    pgm->uid->last = stopBeforeUid;
    searchResultNum = 0;
    mail_search_full(masterStream, NULL, pgm, SE_FREE);
    for (i = 0; i < searchResultNum; i++) {
	RatLogF(interp, RAT_INFO, "downloading", RATLOG_EXPLICIT, i+1,
		searchResultNum);
	envPtr = mail_fetchenvelope(masterStream, searchResultPtr[i]);
	if (*masterErrorPtr) goto done;
	elt = mail_elt(masterStream, searchResultPtr[i]);
	if (*masterErrorPtr) goto done;
	body = mail_fetchtext_full(masterStream, searchResultPtr[i], &len,
				   FT_PEEK);
	if (*masterErrorPtr) goto done;
	header = mail_fetchheader(masterStream, searchResultPtr[i]);
	if (*masterErrorPtr) goto done;
	if (!body || !header) continue;
	Tcl_DStringInit(&ds);
	Tcl_DStringAppend(&ds, header, -1);
	Tcl_DStringAppend(&ds, body, len);
	INIT(&string, mail_string, Tcl_DStringValue(&ds),
	     Tcl_DStringLength(&ds));
	mail_date(datebuf, elt);
	mail_append_full(localStream, localStream->mailbox,
			 RatPurgeFlags(MsgFlags(elt), 0), datebuf, &string);
	Tcl_DStringFree(&ds);
	uid = mail_uid(masterStream, searchResultPtr[i]);
	fprintf(mapFp, "%ld %ld\n", uid, ++localStream->uid_last);
	masterStream->uid_last = uid;
	stateFp = fopen(statebuf, "w");
	fprintf(stateFp, "%ld\n%ld\n", masterStream->uid_validity, uid);
	fclose(stateFp);
	if (mapPtr) {
	    unsigned long *lPtr = (unsigned long*)ckalloc(sizeof(*lPtr));
	    Tcl_HashEntry *entryPtr;
	    int unused;

	    *lPtr = uid;
	    entryPtr = Tcl_CreateHashEntry(mapPtr,
					   (char*)(localStream->uid_last),
					   &unused);
	    Tcl_SetHashValue(entryPtr, lPtr);
	}
    }
 done:
    RatLog(interp, RAT_INFO, "", RATLOG_EXPLICIT);    
    return masterStream->uid_last;
}


/*
 *----------------------------------------------------------------------
 *
 * GetMasterUID
 *
 *	Returns the UID in the master folder of the specified message
 *
 * Results:
 *	The UID is returned or zero if the message is not present
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static unsigned long
GetMasterUID(MAILSTREAM *s, Tcl_HashTable *mapPtr, int index)
{
    Tcl_HashEntry *entryPtr;

    if ((entryPtr = Tcl_FindHashEntry(mapPtr, (char*)mail_uid(s, index+1)))) {
	return *((unsigned long*)Tcl_GetHashValue(entryPtr));
    } else {
	return 0;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * UpdateFolderFlag --
 *
 *	Synchronizes a flag with master
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static void
UpdateFolderFlag(Tcl_Interp *interp, DisFolderInfo *disPtr,
	int index, RatFlag flag, int value)
{
    int local;
    
    local = (*disPtr->getFlagProc)(disPtr->infoPtr, interp, index-1, flag);
    if (value == local) {
	return;
    }
    (*disPtr->setFlagProc)(disPtr->infoPtr, interp, index-1, flag, value);
}


/*
 *----------------------------------------------------------------------
 *
 * ReadMappings --
 *
 *	Reads the mappings-file into the given hash-table
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static void
ReadMappings(MAILSTREAM *s, const char *dir, Tcl_HashTable *mapPtr)
{
    Tcl_HashEntry *entryPtr;
    char buf[1024];
    int unused;
    unsigned long *lPtr, uid;
    FILE *fp;

    snprintf(buf, sizeof(buf), "%s/mappings", dir);
    if (NULL != (fp = fopen(buf, "r"))) {
	buf[sizeof(buf)-1] = '\0';
	while(fgets(buf, sizeof(buf)-1, fp), !feof(fp)) {
	    if (strchr(buf, '<')) {
		ReadOldMappings(s, mapPtr, buf, sizeof(buf)-1, fp);
		break;
	    }
	    buf[strlen(buf)-1] = '\0';
	    uid = atol(strchr(buf, ' '));
	    entryPtr = Tcl_CreateHashEntry(mapPtr, (char*)uid, &unused);
	    lPtr = (unsigned long*)ckalloc(sizeof(unsigned long));
	    *lPtr = atol(buf);
	    Tcl_SetHashValue(entryPtr, lPtr);
	}
	fclose(fp);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * ReadOldMappings --
 *
 *	Reads the old style mappings-file into the given hash-table
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static void
ReadOldMappings(MAILSTREAM *s, Tcl_HashTable *mapPtr, char *buf, int buflen,
	FILE *fp)
{
    Tcl_HashTable tmap;
    Tcl_HashEntry *entryPtr;
    Tcl_HashSearch search;
    unsigned long *lPtr, l, uid;
    ENVELOPE *envPtr;
    int unused;

    /*
     * Read file into local hash-table tmap
     */
    Tcl_InitHashTable(&tmap, TCL_STRING_KEYS);
    do {
	buf[strlen(buf)-1] = '\0';
	entryPtr = Tcl_CreateHashEntry(&tmap, strchr(buf, '<'), &unused);
	lPtr = (unsigned long*)ckalloc(sizeof(unsigned long));
	*lPtr = atol(buf);
	Tcl_SetHashValue(entryPtr, lPtr);
    } while (fgets(buf, buflen, fp), !feof(fp));

    /*
     * Loop through folder and add the new mappings to the real map
     */
    for (l=1; l <= s->nmsgs; l++) {
	envPtr = mail_fetch_structure(s, l, NIL, 0);
	entryPtr = Tcl_FindHashEntry(&tmap, envPtr->message_id);
	if (entryPtr) {
	    uid = *(unsigned long*)Tcl_GetHashValue(entryPtr);
	    entryPtr = Tcl_CreateHashEntry(mapPtr, (char*)mail_uid(s, l),
		    &unused);
	    lPtr = (unsigned long*)ckalloc(sizeof(unsigned long));
	    *lPtr = uid;
	    Tcl_SetHashValue(entryPtr, lPtr);
	}
    }

    /*
     * Free the temporary hashtable from memory
     */
    for (entryPtr = Tcl_FirstHashEntry(&tmap, &search); entryPtr;
	    entryPtr = Tcl_NextHashEntry(&search)) {
	ckfree(Tcl_GetHashValue(entryPtr));
    }
    Tcl_DeleteHashTable(&tmap);
}


/*
 *----------------------------------------------------------------------
 *
 * RatDisFolderDir --
 *
 *	Calculates the directory name of a disconnected folder and
 *	makes sure the directory exists.
 *
 * Results:
 *	A pointer to a static area containign the name
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
char*
RatDisFolderDir(Tcl_Interp *interp, Tcl_Obj *defPtr)
{
    static Tcl_DString ds;
    static int initialized = 0;
    CONST84 char *dir;
    int objc, mobjc;
    Tcl_Obj **objv, **mobjv;

    if (!initialized) {
	Tcl_DStringInit(&ds);
    } else {
	Tcl_DStringSetLength(&ds, 0);
    }

    if (NULL == (dir = RatGetPathOption(interp, "disconnected_dir"))) {
	return NULL;
    }
    Tcl_ListObjGetElements(interp, defPtr, &objc, &objv);
    Tcl_ListObjGetElements(interp,
			   Tcl_GetVar2Ex(interp, "mailServer",
					 Tcl_GetString(objv[3]),
					 TCL_GLOBAL_ONLY),
			   &mobjc, &mobjv);
    Tcl_DStringInit(&ds);
    Tcl_DStringAppend(&ds, dir, -1);
    Tcl_DStringAppend(&ds, "/", 1);
    Tcl_DStringAppend(&ds,Tcl_GetString(mobjv[0]),Tcl_GetCharLength(mobjv[0]));
    Tcl_DStringAppend(&ds, ":", 1);
    if (Tcl_GetCharLength(mobjv[1])) {
	Tcl_DStringAppend(&ds, Tcl_GetString(mobjv[1]),
			  Tcl_GetCharLength(mobjv[1]));
    } else {
	Tcl_DStringAppend(&ds, "143", 3);
    }
    Tcl_DStringAppend(&ds, "/", 1);
    if (Tcl_GetCharLength(objv[4])) {
	Tcl_DStringAppend(&ds, Tcl_GetString(objv[4]),
			  Tcl_GetCharLength(objv[4]));
    } else {
	Tcl_DStringAppend(&ds, "INBOX", 5);
    }
    Tcl_DStringAppend(&ds, "+", 1);
    Tcl_DStringAppend(&ds,Tcl_GetString(mobjv[3]),Tcl_GetCharLength(mobjv[3]));
    Tcl_DStringAppend(&ds, "+imap", 5);
    if (CreateDir(Tcl_DStringValue(&ds))) {
	return NULL;
    }
    return Tcl_DStringValue(&ds);
}


/*
 *----------------------------------------------------------------------
 *
 * Dis_SyncProc --
 *
 *	Synchronizes the specified folder
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The folder may be modified
 *
 *
 *----------------------------------------------------------------------
 */
static int
Dis_SyncProc(RatFolderInfoPtr infoPtr, Tcl_Interp *interp)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    struct stat sbuf;
    char buf[1024];

    snprintf(buf, sizeof(buf), "%s/master", disPtr->dir);
    stat(buf, &sbuf);
    return Dis_SyncFolder(interp, disPtr->dir, sbuf.st_size, 1, NULL);
}


/*
 *----------------------------------------------------------------------
 *
 * InitUidMap --
 *
 *	initializes the uid map
 *
 * Results:
 *	None
 *
 * Side effects:
 *	The uidMap structure is initialized
 *
 *----------------------------------------------------------------------
 */
static RatUidMap*
InitUidMap(MAILSTREAM *s)
{
    unsigned long i;
    RatUidMap *uidMap;

    uidMap = (RatUidMap*)ckalloc(sizeof(RatUidMap));
    uidMap->allocated = s->nmsgs+32;
    uidMap->map = (unsigned long*)
	ckalloc(uidMap->allocated*sizeof(unsigned long));
    uidMap->size = s->nmsgs;
    for (i=0; i<s->nmsgs; i++) {
	uidMap->map[i] = mail_uid(s, i+1);
    }
    return uidMap;
}


/*
 *----------------------------------------------------------------------
 *
 * FreeUidMap --
 *
 *	Frees the uid map
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
static void
FreeUidMap(RatUidMap *uidMap)
{
    ckfree(uidMap->map);
    ckfree(uidMap);
}


/*
 *----------------------------------------------------------------------
 *
 * MsgNo --
 *
 *	Lookup a message uid in the map and return the msgno
 *
 * Results:
 *	The msgno for the given uid or 0 if there is no such message
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
static unsigned long
MsgNo(RatUidMap *uidMap, unsigned long uid)
{
    unsigned long i;

    for (i=0; i<uidMap->size; i++) {
	if (uidMap->map[i] == uid) {
	    return i+1;
	}
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * CheckDeletion --
 *
 *      Checks which messages are going to be deleted before an expunge
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Appends things to the changes file
 *
 *----------------------------------------------------------------------
 */
static void
CheckDeletion(RatFolderInfoPtr infoPtr, Tcl_Interp *interp)
{
    DisFolderInfo *disPtr = (DisFolderInfo*)infoPtr->private2;
    Tcl_HashEntry *entryPtr;
    FILE *fp = NULL;
    char buf[1024];
    unsigned long uid;
    int i;

    for (i = 0; i < infoPtr->number; i++) {
	if (0 != (*disPtr->getFlagProc)(infoPtr, interp, i, RAT_DELETED)) {
	    if (NULL == fp) {
		snprintf(buf, sizeof(buf), "%s/changes", disPtr->dir);
		fp = fopen(buf, "a");
	    }
	    uid = GetMasterUID(((StdFolderInfo*)infoPtr->private)->stream,
		    &disPtr->map, i);
	    if (uid && NULL != fp) {
		fprintf(fp, "delete %ld\n", uid);
	    }
	    entryPtr = Tcl_FindHashEntry(&disPtr->map, (char*)
		    mail_uid(((StdFolderInfo*)infoPtr->private)->stream,
			     i+1));
	    if (entryPtr) {
		disPtr->mapChanged = 1;
		ckfree(Tcl_GetHashValue(entryPtr));
		Tcl_DeleteHashEntry(entryPtr);
	    }
	}
    }
    if (NULL != fp) {
	fclose(fp);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DisUploadMsg --
 *
 *      Uploads the given message to the master folder
 *
 * Results:
 *	The uid of the newly uploaded messages
 *
 * Side effects:
 *	Modifies master folder
 *
 *
 *----------------------------------------------------------------------
 */

static unsigned long
DisUploadMsg(MAILSTREAM *masterStream, MAILSTREAM *localStream,
	     const char *subject, const char *in_reply_to,
	     const char *message_id, char *envdate,
	     unsigned long local_uid,
	     Tcl_DString *message, char *date,
	     char *flags, FILE *mapFP, Tcl_HashTable *mapPtr)
{
    Tcl_HashEntry *entryPtr;
    SEARCHPGM *pgm;
    STRING string;
    unsigned long *lPtr, uid;
    int unused;

    uid = masterStream->uid_last;
    
    INIT(&string, mail_string, Tcl_DStringValue(message),
	 Tcl_DStringLength(message));
    RatPurgeFlags(flags, 0);
    mail_append_full(masterStream, masterStream->mailbox, flags, date,&string);

    pgm = mail_newsearchpgm();
    if (subject && *subject) {
	pgm->subject = mail_newstringlist();
	pgm->subject->text.data = (unsigned char*)cpystr(subject);
	pgm->subject->text.size = strlen(subject);
    }
    if (in_reply_to && *in_reply_to) {
	pgm->in_reply_to = mail_newstringlist();
	pgm->in_reply_to->text.data = (unsigned char*)cpystr(in_reply_to);
	pgm->in_reply_to->text.size = strlen(in_reply_to);
    }
    if (message_id && *message_id) {
	pgm->message_id = mail_newstringlist();
	pgm->message_id->text.data = (unsigned char*)cpystr(message_id);
	pgm->message_id->text.size = strlen(message_id);
    }
    pgm->uid = mail_newsearchset();
    pgm->uid->first = uid+1;
    pgm->uid->last = 0;
    if (envdate && *envdate) {
	pgm->header = mail_newsearchheader("date", envdate);
    }
    searchResultNum = 0;
    mail_search_full(masterStream, NULL, pgm, SE_FREE|SE_UID);
    if (searchResultNum == 1) {
	fprintf(mapFP, "%ld %ld\n", searchResultPtr[0], local_uid);
	if (mapPtr) {
	    lPtr = (unsigned long*)ckalloc(sizeof(*lPtr));
	    *lPtr = searchResultPtr[0];
	    entryPtr = Tcl_CreateHashEntry(mapPtr, (char*)local_uid, &unused);
	    Tcl_SetHashValue(entryPtr, lPtr);
	}
	masterStream->uid_last = searchResultPtr[0];
	return searchResultPtr[0];
    } else {
	return 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Handle* --
 *
 *      Handle events from mailbox
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */

static void
Dis_HandleExists(void *state, unsigned long nmsg)
{ 
    DisFolderInfo *disPtr = (DisFolderInfo *) state;
    disPtr->exists = nmsg;
}

static void
Dis_HandleExpunged(void *state, unsigned long index)
{ 
    DisFolderInfo *disPtr = (DisFolderInfo *) state;

    disPtr->expunged++;
}

/*
 *----------------------------------------------------------------------
 *
 * WriteMappings --
 *
 *      Writes the mappings-file
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
static void
WriteMappings(DisFolderInfo *disPtr)
{
    Tcl_HashEntry *entryPtr;
    Tcl_HashSearch search;
    unsigned long *lPtr;
    char buf[1024];
    FILE *fp;
    
    if (!disPtr->mapChanged) {
	return;
    }
    snprintf(buf, sizeof(buf), "%s/mappings", disPtr->dir);
    fp = fopen(buf, "w");
    for (entryPtr = Tcl_FirstHashEntry(&disPtr->map, &search); entryPtr;
	    entryPtr = Tcl_NextHashEntry(&search)) {
	lPtr = (unsigned long*)Tcl_GetHashValue(entryPtr);
	fprintf(fp, "%ld %ld\n", *lPtr,
		    (unsigned long)Tcl_GetHashKey(&disPtr->map, entryPtr));
    }
    fclose(fp);
    disPtr->mapChanged = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * RatDisOnOffTrans	--
 *
 *      Handle transitions between online and offline state
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Opens/closes folders
 *
 *
 *----------------------------------------------------------------------
 */
int
RatDisOnOffTrans(Tcl_Interp *interp, int newState)
{
    Tcl_HashEntry *entryPtr;
    Tcl_HashSearch search;
    RatFolderInfo *infoPtr;
    DisFolderInfo *disPtr;
    char buf[1024];
    struct stat sbuf;
    int count = 0, allfail = 1;

    for (entryPtr = Tcl_FirstHashEntry(&openDisFolders, &search);
	 entryPtr;
	 entryPtr = Tcl_NextHashEntry(&search), count++) {
	infoPtr = Tcl_GetHashValue(entryPtr);
	disPtr = (DisFolderInfo*)infoPtr->private2;
	
	if (newState && !disPtr->master) {
	    /* Go online */
	    snprintf(buf, sizeof(buf), "%s/master", disPtr->dir);
	    stat(buf, &sbuf);
	    if (TCL_OK == Dis_SyncFolder(interp,disPtr->dir, sbuf.st_size,
					 1, &disPtr->master)) {
		allfail = 0;
	    }
	    
	} else if (!newState && disPtr->master) {
	    /* Go offline */
	    Std_StreamClose(interp, disPtr->master);
	    disPtr->master = NULL;
	    allfail = 0;
	}
    }
    if (!newState) {
	/*
	 * Force closing of all pending connections
	 */
	Std_StreamCloseAllCached(interp);
    }
    if (allfail && 0 != count) {
	return TCL_ERROR;
    } else {
	return TCL_OK;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RatDisManageFolder --
 *
 *      Create or delete folders
 *
 * Results:
 *	None
 *
 * Side effects:
 *	None
 *
 *
 *----------------------------------------------------------------------
 */
void
RatDisManageFolder(Tcl_Interp *interp, RatManagementAction op, Tcl_Obj *fPtr)
{
    struct dirent *dirent;
    const char *dirname;
    char buf[1024];
    DIR *dir;
    int i;

    if (NULL == (dirname = PrepareDir(interp, fPtr))) {
	return;
    }
    if (RAT_MGMT_DELETE == op) {
	if (NULL == (dir = opendir(dirname))) {
	    return;
	}
	while (NULL != (dirent = readdir(dir))) {
	    if (!strcmp(".", dirent->d_name) || !strcmp("..", dirent->d_name)){
		continue;
	    }
	    snprintf(buf, sizeof(buf), "%s/%s", dirname, dirent->d_name);
	    unlink(buf);
	}
	closedir(dir);
	i = rmdir(dirname);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1