/* * 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; imaster, 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; inmsgs; 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; isize; 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); } }