/*
* ratFolder.c --
*
* This file contains basic support code for the folder commands. Each
* folder type is created using an unique command. This command returns
* a folder handler, which when invoked calls the RatFolderCmd()
* procedure with a pointer to a RatFolderInfo structure as clientData.
*
* TkRat software and its included text is Copyright 1996-2002 by
* Martin Forssén
*
* The full text of the legal notices is contained in the file called
* COPYRIGHT, included with this distribution.
*/
#include "ratFolder.h"
/*
* This structure is used to hold the data while sorting
*/
typedef struct SortData {
char *msgid;
char *ref;
char *subject;
char *sender;
time_t date;
long size;
struct SortData *nextPtr;
int prev, next, child, parent;
Tcl_Obj *tPtr;
} SortData;
/*
* Global list of folders
*/
RatFolderInfo *ratFolderList = NULL;
/*
* The number of folders opened. This is used when making
* the folder entities.
*/
static int numFolders = 0;
/*
* Sort order names
*/
struct {
SortOrder order;
int reverse;
char *name;
} sortNames[] = {
{SORT_THREADED, 0, "threaded"},
{SORT_SUBJDATE, 0, "subject"},
{SORT_SENDERDATE, 0, "sender"},
{SORT_SUBJECT, 0, "subjectonly"},
{SORT_SENDER, 0, "senderonly"},
{SORT_DATE, 0, "date"},
{SORT_NONE, 0, "folder"},
{SORT_NONE, 1, "reverseFolder"},
{SORT_DATE, 1, "reverseDate"},
{SORT_SIZE, 0, "size"},
{SORT_SIZE, 1, "reverseSize"},
{0, 0, NULL}
};
/*
* Flag names
* The entries in this list must be synchronized with the enum type RatFlag
* defined in RatFolder.h
*/
flag_name_t flag_name[] = {
{ "\\Seen", "seen", 'R' },
{ "\\Deleted", "deleted", 'D' },
{ "\\Flagged", "flagged", 'F' },
{ "\\Answered", "answered", 'A' },
{ "\\Draft", "draft", 'T' },
{ "\\Recent", "recent", '\0' },
{ NULL, NULL, '\0'}
};
/*
* Global variable used to controll the sorting functions
*/
static SortData *baseSortDataPtr;
/*
* Global id to handle folder list updates
*/
static int folderChangeId = 0;
/*
* Possible flag values for remote hosts
*/
#ifdef HAVE_OPENSSL
static char *cClientFlags[] = {
"/notls", "/ssl", "/novalidate-cert", "/secure", NULL
};
#endif /* HAVE_OPENSSL */
static Tcl_ObjCmdProc RatOpenFolder;
static Tcl_ObjCmdProc RatFolderCmd;
static void RatFolderSort(Tcl_Interp *interp, RatFolderInfo *infoPtr);
static int RatFolderSortCompareDate(const void *arg1, const void *arg2);
static int RatFolderSortCompareSize(const void *arg1, const void *arg2);
static int RatFolderSortCompareSubject(const void *arg1, const void *arg2);
static int RatFolderSortCompareSender(const void *arg1, const void *arg2);
static int IsChild(SortData *dataPtr, int child, int parent);
static int RatFolderSortLinearize(int *p, int n, SortData *dataPtr, int first,
int depth);
static Tcl_TimerProc RatFolderUpdateTime;
static Tcl_ObjCmdProc RatManageFolder;
static RatFlag RatFlagNameToInt(const char *name);
static char *RatGetIdentDef(Tcl_Interp *interp, Tcl_Obj *defPtr);
/*
*----------------------------------------------------------------------
*
* RatFolderInit --
*
* Initializes the folder commands.
*
* 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:
* The folder creation commands are created in interp. And the
* Std folder is initialized.
*
*
*----------------------------------------------------------------------
*/
int
RatFolderInit(Tcl_Interp *interp)
{
RatInitMessages();
if (TCL_OK != RatStdFolderInit(interp)) {
return TCL_ERROR;
}
if (TCL_OK != RatDbFolderInit(interp)) {
return TCL_ERROR;
}
if (TCL_OK != RatDisFolderInit(interp)) {
return TCL_ERROR;
}
Tcl_CreateObjCommand(interp, "RatOpenFolder", RatOpenFolder,
(ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateObjCommand(interp, "RatParseExp", RatParseExpCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "RatGetExp", RatGetExpCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "RatFreeExp", RatFreeExpCmd, NULL, NULL);
Tcl_CreateObjCommand(interp, "RatCreateFolder", RatManageFolder,
(void*)RAT_MGMT_CREATE, NULL);
Tcl_CreateObjCommand(interp, "RatDeleteFolder", RatManageFolder,
(void*)RAT_MGMT_DELETE, NULL);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* RatGetOpenFolder --
*
* Search the list of open folders for a folder matching the given
* definition.
*
* Results:
* If a match is found then that folders reference count is incremented
* and the infoPtr is returned. Otherwise NULL is returned.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
RatFolderInfo*
RatGetOpenFolder(Tcl_Interp *interp, Tcl_Obj *defPtr)
{
RatFolderInfo *infoPtr;
char *def = RatGetIdentDef(interp, defPtr);
for (infoPtr = ratFolderList;
infoPtr && strcmp(infoPtr->definition, def);
infoPtr = infoPtr->nextPtr);
if (infoPtr) {
infoPtr->refCount++;
}
return infoPtr;
}
/*
*----------------------------------------------------------------------
*
* RatOpenFolder --
*
* See the INTERFACE specification
*
* Results:
* The return value is normally TCL_OK and a foilder handle is left
* in the result area; if something goes wrong TCL_ERROR is returned
* and an error message will be left in the result area.
*
* Side effects:
* The folder creation commands are created in interp.
*
*
*----------------------------------------------------------------------
*/
static int
RatOpenFolder(ClientData clientData, Tcl_Interp *interp, int objc,
Tcl_Obj *CONST objv[])
{
RatFolderInfo *infoPtr;
int i, fobjc, lobjc;
CONST84 char *sortName = NULL;
Tcl_Obj **fobjv, **lobjv, *role = NULL, *oPtr;
if (objc != 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
Tcl_GetString(objv[0]), " folderdef\"", (char *) NULL);
return TCL_ERROR;
}
/*
* Check open folders
*/
if ((infoPtr = RatGetOpenFolder(interp, objv[1]))) {
Tcl_SetResult(interp, infoPtr->cmdName, TCL_VOLATILE);
return TCL_OK;
}
Tcl_ListObjGetElements(interp, objv[1], &fobjc, &fobjv);
if (!strcmp(Tcl_GetString(fobjv[1]), "dbase")) {
infoPtr = RatDbFolderCreate(interp, objv[1]);
} else if (!strcmp(Tcl_GetString(fobjv[1]), "dis")) {
infoPtr = RatDisFolderCreate(interp, objv[1]);
} else {
infoPtr = RatStdFolderCreate(interp, objv[1]);
}
if (NULL == infoPtr) {
Tcl_AppendResult(interp, "Failed to create folder", NULL);
return TCL_ERROR;
}
Tcl_ListObjGetElements(interp, fobjv[2], &lobjc, &lobjv);
for (i=0; i < lobjc; i+=2) {
if (!strcmp("sort", Tcl_GetString(lobjv[i]))) {
sortName = Tcl_GetString(lobjv[i+1]);
}
if (!strcmp("role", Tcl_GetString(lobjv[i]))) {
role = lobjv[i+1];
}
}
infoPtr->definition = cpystr(RatGetIdentDef(interp, objv[1]));
ckfree(infoPtr->name);
infoPtr->name = cpystr(Tcl_GetString(fobjv[0]));
infoPtr->refCount = 1;
oPtr = Tcl_GetVar2Ex(interp, "option", "watcher_time", TCL_GLOBAL_ONLY);
if (TCL_OK != Tcl_GetIntFromObj(interp, oPtr, &infoPtr->watcherInterval)) {
infoPtr->watcherInterval = 30;
}
if (infoPtr->watcherInterval > 1000000) {
infoPtr->watcherInterval = 1000000;
}
if (!sortName || !strcmp("default", sortName)) {
sortName = Tcl_GetVar2(interp, "option","folder_sort",TCL_GLOBAL_ONLY);
}
for (i=0; sortNames[i].name && strcmp(sortNames[i].name, sortName); i++);
if (sortNames[i].name) {
infoPtr->sortOrder = sortNames[i].order;
infoPtr->reverse = sortNames[i].reverse;
} else {
infoPtr->sortOrder = SORT_NONE;
infoPtr->reverse = 0;
}
if (!role || !strcmp("default", Tcl_GetString(role))) {
role = Tcl_GetVar2Ex(interp, "option", "default_role",TCL_GLOBAL_ONLY);
}
infoPtr->role = role;
Tcl_IncrRefCount(infoPtr->role);
infoPtr->sortOrderChanged = 0;
infoPtr->cmdName = ckalloc(16);
infoPtr->allocated = infoPtr->number;
infoPtr->msgCmdPtr = (char **) ckalloc(infoPtr->allocated*sizeof(char*));
infoPtr->privatePtr = (ClientData**)ckalloc(
infoPtr->allocated*sizeof(ClientData));
for (i=0; i<infoPtr->allocated; i++) {
infoPtr->msgCmdPtr[i] = (char *) NULL;
infoPtr->privatePtr[i] = (ClientData*) NULL;
}
(*infoPtr->initProc)(infoPtr, interp, -1);
infoPtr->presentationOrder = (int*)ckalloc(infoPtr->allocated*sizeof(int));
infoPtr->hidden = (int*) ckalloc(infoPtr->allocated*sizeof(int));
infoPtr->flagsChanged = 0;
infoPtr->nextPtr = ratFolderList;
if (infoPtr->finalProc) {
(*infoPtr->finalProc)(infoPtr, interp);
}
ratFolderList = infoPtr;
RatFolderSort(interp, infoPtr);
sprintf(infoPtr->cmdName, "RatFolder%d", numFolders++);
Tcl_CreateObjCommand(interp, infoPtr->cmdName, RatFolderCmd,
(ClientData) infoPtr, (Tcl_CmdDeleteProc *) NULL);
Tcl_SetVar2Ex(interp, "folderExists", infoPtr->cmdName,
Tcl_NewIntObj(infoPtr->visible), TCL_GLOBAL_ONLY);
Tcl_SetVar2Ex(interp, "folderRecent", infoPtr->cmdName,
Tcl_NewIntObj(infoPtr->recent), TCL_GLOBAL_ONLY);
Tcl_SetVar2Ex(interp, "folderUnseen", infoPtr->cmdName,
Tcl_NewIntObj(infoPtr->unseen), TCL_GLOBAL_ONLY);
Tcl_SetVar2Ex(interp, "folderChanged", infoPtr->cmdName,
Tcl_NewIntObj(++folderChangeId), TCL_GLOBAL_ONLY);
Tcl_SetResult(interp, infoPtr->cmdName, TCL_VOLATILE);
if (infoPtr->watcherInterval) {
infoPtr->timerToken = Tcl_CreateTimerHandler(
infoPtr->watcherInterval*1000, RatFolderUpdateTime,
(ClientData)infoPtr);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* RatFolderCmd --
*
* Main folder entity procedure. This procedure implements the
* folder commands mentioned in ../doc/interface. In order to make
* this a tad easier it uses the procedures defined in the
* RatFolderInfo structure :-)
*
* Results:
* Depends on the input :-)
*
* Side effects:
* The specified folder may be modified.
*
*
*----------------------------------------------------------------------
*/
int
RatFolderCmd(ClientData clientData, Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[])
{
RatFolderInfo *infoPtr = (RatFolderInfo*) clientData;
Tcl_Obj *oPtr;
if (objc < 2) goto usage;
if (!strcmp(Tcl_GetString(objv[1]), "update")) {
RatUpdateType mode;
if (objc != 3) goto usage;
if (!strcmp(Tcl_GetString(objv[2]), "update")) {
mode = RAT_UPDATE;
} else if (!strcmp("checkpoint", Tcl_GetString(objv[2]))) {
if (!infoPtr->flagsChanged) {
Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
return TCL_OK;
}
mode = RAT_CHECKPOINT;
infoPtr->flagsChanged = 0;
} else if (!strcmp(Tcl_GetString(objv[2]), "sync")) {
mode = RAT_SYNC;
} else {
goto usage;
}
return RatUpdateFolder(interp, infoPtr, mode);
} else if (!strcmp(Tcl_GetString(objv[1]), "close")) {
int force = 0;
if (objc != 2 &&
(objc != 3
|| TCL_OK != Tcl_GetBooleanFromObj(interp,objv[2],&force))) {
goto usage;
}
return RatFolderClose(interp, infoPtr, force);
} else if (!strcmp(Tcl_GetString(objv[1]), "setName")) {
if (objc != 3) goto usage;
ckfree(infoPtr->name);
infoPtr->name = (char *) ckalloc(strlen(Tcl_GetString(objv[2]))+1);
strcpy(infoPtr->name, Tcl_GetString(objv[2]));
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "info")) {
int infoArgc;
CONST84 char *infoArgv[3];
char numberBuf[16], sizeBuf[16];
char *list;
if (objc != 2) goto usage;
sprintf(numberBuf, "%d", infoPtr->visible);
sprintf(sizeBuf, "%d", infoPtr->size);
infoArgv[0] = infoPtr->name;
infoArgv[1] = numberBuf;
infoArgv[2] = sizeBuf;
infoArgc = 3;
list = Tcl_Merge(infoArgc, infoArgv);
Tcl_SetResult(interp, list, TCL_DYNAMIC);
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "list")) {
ListExpression *exprPtr;
Tcl_Obj *oPtr, *rPtr;
int i;
if (objc != 3) goto usage;
if (NULL == (exprPtr = RatParseList(Tcl_GetString(objv[2])))) {
Tcl_SetResult(interp, "Illegal list format", TCL_STATIC);
return TCL_ERROR;
}
rPtr = Tcl_NewObj();
for (i=0; i < infoPtr->visible; i++) {
oPtr = RatDoList(interp, exprPtr, infoPtr->infoProc,
(ClientData)infoPtr, infoPtr->presentationOrder[i]);
Tcl_ListObjAppendElement(interp, rPtr, oPtr);
}
RatFreeListExpression(exprPtr);
Tcl_SetObjResult(interp, rPtr);
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "get")) {
int index;
if (objc != 3
|| TCL_OK != Tcl_GetIntFromObj(interp, objv[2],&index)) goto usage;
if (index < 0 || index >= infoPtr->visible) {
Tcl_SetResult(interp, "Index is out of bounds", TCL_STATIC);
return TCL_ERROR;
}
if (NULL == infoPtr->msgCmdPtr[infoPtr->presentationOrder[index]]) {
infoPtr->msgCmdPtr[infoPtr->presentationOrder[index]] =
(*infoPtr->createProc)(infoPtr, interp,
infoPtr->presentationOrder[index]);
}
Tcl_SetResult(interp,
infoPtr->msgCmdPtr[infoPtr->presentationOrder[index]],
TCL_VOLATILE);
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "setFlag")) {
int index, value, unseen, recent;
RatFlag flag;
if (objc != 5
|| TCL_OK != Tcl_GetIntFromObj(interp, objv[2],&index)
|| TCL_OK != Tcl_GetBooleanFromObj(interp, objv[4], &value)) {
goto usage;
}
if (index < 0 || index >= infoPtr->visible) {
Tcl_SetResult(interp, "Index is out of bounds", TCL_STATIC);
return TCL_ERROR;
}
flag = RatFlagNameToInt(Tcl_GetString(objv[3]));
if (value != (*infoPtr->getFlagProc)(infoPtr, interp,
infoPtr->presentationOrder[index], flag)) {
recent = infoPtr->recent;
unseen = infoPtr->unseen;
(*infoPtr->setFlagProc)(infoPtr, interp,
infoPtr->presentationOrder[index], flag, value);
infoPtr->flagsChanged = 1;
if (infoPtr->recent != recent) {
Tcl_SetVar2Ex(interp, "folderRecent", infoPtr->cmdName,
Tcl_NewIntObj(infoPtr->recent), TCL_GLOBAL_ONLY);
}
if (infoPtr->unseen != unseen) {
oPtr = Tcl_SetVar2Ex(interp, "folderUnseen", infoPtr->cmdName,
Tcl_NewIntObj(infoPtr->unseen), TCL_GLOBAL_ONLY);
}
}
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "getFlag")) {
int index;
RatFlag flag;
if (objc != 4
|| TCL_OK != Tcl_GetIntFromObj(interp, objv[2],&index)) goto usage;
if (index < 0 || index >= infoPtr->visible) {
Tcl_SetResult(interp, "Index is out of bounds", TCL_STATIC);
return TCL_ERROR;
}
flag = RatFlagNameToInt(Tcl_GetString(objv[3]));
Tcl_SetObjResult(interp, Tcl_NewIntObj(
(*infoPtr->getFlagProc)(infoPtr, interp,
infoPtr->presentationOrder[index],flag)));
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "flagged")) {
RatFlag flag;
char buf[64];
int i;
if (objc != 3) goto usage;
flag = RatFlagNameToInt(Tcl_GetString(objv[2]));
Tcl_ResetResult(interp);
for (i=0; i<infoPtr->visible; i++) {
if ((*infoPtr->getFlagProc)(infoPtr, interp,
infoPtr->presentationOrder[i], flag)) {
sprintf(buf, "%d", i);
Tcl_AppendElement(interp, buf);
}
}
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "insert")) {
Tcl_CmdInfo cmdInfo;
char **msgv;
int i, ret;
if (objc < 3) goto usage;
for(i=2; i<objc; i++) {
if (0 == Tcl_GetCommandInfo(interp,Tcl_GetString(objv[i]),&cmdInfo)
|| NULL == cmdInfo.objClientData) {
Tcl_AppendResult(interp, "error \"", Tcl_GetString(objv[i]),
"\" is not a valid message command", (char *) NULL);
return TCL_ERROR;
}
}
msgv = (char**)ckalloc((objc-2)*sizeof(char*));
for (i=2; i<objc; i++) {
msgv[i-2] = Tcl_GetString(objv[i]);
}
ret = RatFolderInsert(interp, infoPtr, objc-2, msgv);
ckfree(msgv);
return ret;
} else if (!strcmp(Tcl_GetString(objv[1]), "type")) {
Tcl_SetResult(interp, infoPtr->type, TCL_STATIC);
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "find")) {
int msgNo, i;
if (objc != 3) goto usage;
Tcl_SetObjResult(interp, Tcl_NewIntObj(-1));
for (msgNo=0; msgNo < infoPtr->number; msgNo++) {
if (infoPtr->msgCmdPtr[msgNo]
&& !strcmp(infoPtr->msgCmdPtr[msgNo],
Tcl_GetString(objv[2]))) {
for (i=0; msgNo != infoPtr->presentationOrder[i]; i++);
Tcl_SetObjResult(interp, Tcl_NewIntObj(i));
break;
}
}
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "match")) {
int i, expId;
if (objc != 3
|| TCL_OK != Tcl_GetIntFromObj(interp, objv[2],&expId)) goto usage;
oPtr = Tcl_NewObj();
for (i=0; i<infoPtr->number; i++) {
if (RatExpMatch(interp, expId, infoPtr->infoProc,
(ClientData)infoPtr, infoPtr->presentationOrder[i])) {
Tcl_ListObjAppendElement(interp, oPtr, Tcl_NewIntObj(i));
}
}
Tcl_SetObjResult(interp, oPtr);
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "setSortOrder")) {
CONST84 char *name = Tcl_GetString(objv[2]);
int i, j;
if (objc != 3) goto usage;
if (!strcmp("default", name)) {
name = Tcl_GetVar2(interp, "option","folder_sort",TCL_GLOBAL_ONLY);
}
for (i=0; sortNames[i].name && strcmp(sortNames[i].name, name); i++);
if (sortNames[i].name) {
/*
* If the old sort order was threaded and the new one is not then
* we should clear the threading info
*/
if (infoPtr->sortOrder == SORT_THREADED
&& sortNames[i].order != SORT_THREADED) {
for (j=0; j<infoPtr->number; j++) {
(*infoPtr->setInfoProc)(interp,(ClientData)infoPtr,
RAT_FOLDER_THREADING, j, NULL);
}
}
infoPtr->sortOrder = sortNames[i].order;
infoPtr->reverse = sortNames[i].reverse;
infoPtr->sortOrderChanged = 1;
return TCL_OK;
} else {
Tcl_SetResult(interp, "No such sort order", TCL_STATIC);
return TCL_ERROR;
}
} else if (!strcmp(Tcl_GetString(objv[1]), "getSortOrder")) {
int i;
for (i=0; sortNames[i].name; i++) {
if (infoPtr->sortOrder == sortNames[i].order
&& infoPtr->reverse == sortNames[i].reverse) {
Tcl_SetResult(interp, sortNames[i].name, TCL_STATIC);
}
}
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "netsync")) {
if (!infoPtr->syncProc) {
Tcl_AppendResult(interp, "Operation unsupported on this folder",
(char *) NULL);
return TCL_ERROR;
}
return (*infoPtr->syncProc)(infoPtr, interp);
} else if (!strcmp(Tcl_GetString(objv[1]), "refcount")) {
Tcl_SetObjResult(interp, Tcl_NewIntObj(infoPtr->refCount));
return TCL_OK;
} else if (!strcmp(Tcl_GetString(objv[1]), "role")) {
Tcl_SetObjResult(interp, infoPtr->role);
return TCL_OK;
}
usage:
Tcl_AppendResult(interp, "Illegal usage of \"", Tcl_GetString(objv[0]),
"\"", (char *) NULL);
return TCL_ERROR;
}
/*
*----------------------------------------------------------------------
*
* RatFolderSort --
*
* Sorts the folder according to the users wishes. The user may
* communicates their will via the folder_sort variable. Currently
* The following methods are implemented:
* subjectonly - Alphabetically on subject
* sender - Alphabetically on sender name
* folder - Sorts in native folder order
* reverseFolder - The reverse of the above
* date - By date sent
* reverseDate - By reverse date sent
* subject - Group messages with the same subject
* and sort the groups by the earliest date
* in each group.
*
* Results:
* None.
*
* Side effects:
* The presentation order member of the RatFolderInfo structure
* is initialized. The size of the folder is updated.
*
* TODO, convert to Tcl_Obj
*
*----------------------------------------------------------------------
*/
static void
RatFolderSort(Tcl_Interp *interp, RatFolderInfo *infoPtr)
{
int i, j, k, pi, pj, snarf, numParm, seen, *tmpPtr, first, last,
*p=infoPtr->presentationOrder,
needDate=0, needSubject=0, needSender=0, needIds = 0, needSize = 0,
*uniqList, uniqListUsed, *subList, *lengthList;
SortData *dataPtr, *dPtr;
Tcl_Obj *oPtr, *o2Ptr;
if (0 == infoPtr->number) {
infoPtr->visible = 0;
return;
}
oPtr = Tcl_GetVar2Ex(interp, "option","dsn_snarf_reports",TCL_GLOBAL_ONLY);
Tcl_GetBooleanFromObj(interp, oPtr, &snarf);
switch(infoPtr->sortOrder) {
case SORT_NONE:
break;
case SORT_SUBJECT:
needSubject = 1;
break;
case SORT_THREADED:
needIds = 1;
needDate = 1;
needSubject = 1;
break;
case SORT_SUBJDATE:
needDate = 1;
needSubject = 1;
break;
case SORT_SENDER:
needSender = 1;
break;
case SORT_SENDERDATE:
needDate = 1;
needSender = 1;
break;
case SORT_DATE:
needDate = 1;
break;
case SORT_SIZE:
needSize = 1;
break;
}
dataPtr = (SortData*)ckalloc(infoPtr->number*sizeof(*dataPtr));
infoPtr->size = 0;
for (i=0; i<infoPtr->number; i++) {
infoPtr->hidden[i] = 0;
infoPtr->presentationOrder[i] = i;
oPtr = (*infoPtr->infoProc)(interp, (ClientData)infoPtr,
RAT_FOLDER_TYPE, i);
if (oPtr && !strcasecmp(Tcl_GetString(oPtr), "multipart/report")) {
oPtr = (*infoPtr->infoProc)(interp, (ClientData)infoPtr,
RAT_FOLDER_PARAMETERS, i);
if (!oPtr) {
continue;
}
Tcl_ListObjLength(interp, oPtr, &numParm);
for (j=0; j<numParm; j++) {
Tcl_ListObjIndex(interp, oPtr, j, &o2Ptr);
if (!strcasecmp(Tcl_GetString(o2Ptr),
"report-type delivery-status")) {
/*
* This is a DSN; call the proper authorities
*/
seen=(*infoPtr->getFlagProc)(infoPtr, interp, i, RAT_SEEN);
if (0 == seen) {
(*infoPtr->setFlagProc)(infoPtr, interp, i,
RAT_SEEN, 1);
}
if (!infoPtr->msgCmdPtr[i]) {
infoPtr->msgCmdPtr[i] =
(*infoPtr->createProc)(infoPtr, interp, i);
}
if (RatDSNHandle(interp, infoPtr->msgCmdPtr[i]) && snarf) {
if ((*infoPtr->getFlagProc)(
infoPtr, interp, i, RAT_RECENT)){
infoPtr->recent--;
}
infoPtr->hidden[i] = 1;
(*infoPtr->setFlagProc)(infoPtr, interp, i,
RAT_DELETED, 1);
} else {
(*infoPtr->setFlagProc)(infoPtr, interp, i,
RAT_SEEN, seen);
}
break;
}
}
}
if (!infoPtr->hidden[i]) {
oPtr = (*infoPtr->infoProc)(interp, (ClientData)infoPtr,
RAT_FOLDER_SIZE,i) ;
if (oPtr) {
Tcl_GetIntFromObj(interp, oPtr, &j);
infoPtr->size += j;
}
}
if (needSubject) {
oPtr = (*infoPtr->infoProc)(interp, (ClientData)infoPtr,
RAT_FOLDER_CANONSUBJECT, i);
dataPtr[i].subject = Tcl_GetString(oPtr);
}
if (needSender) {
oPtr = (*infoPtr->infoProc)(interp,(ClientData)infoPtr,
RAT_FOLDER_NAME, i);
dataPtr[i].sender = Tcl_GetString(oPtr);
dataPtr[i].sender = cpystr(dataPtr[i].sender);
lcase(dataPtr[i].sender);
}
if (needDate) {
long myLong;
oPtr = (*infoPtr->infoProc)(interp,(ClientData)infoPtr,
RAT_FOLDER_DATE_N, i);
Tcl_GetLongFromObj(interp, oPtr, &myLong);
dataPtr[i].date = (time_t) myLong;
}
if (needSize) {
oPtr = (*infoPtr->infoProc)(interp,(ClientData)infoPtr,
RAT_FOLDER_SIZE, i);
Tcl_GetLongFromObj(interp, oPtr, &dataPtr[i].size);
}
if (needIds) {
oPtr = (*infoPtr->infoProc)(interp,(ClientData)infoPtr,
RAT_FOLDER_MSGID, i);
if (oPtr) {
dataPtr[i].msgid = Tcl_GetString(oPtr);
} else {
dataPtr[i].msgid = "";
}
oPtr = (*infoPtr->infoProc)(interp,(ClientData)infoPtr,
RAT_FOLDER_REF, i);
if (oPtr) {
dataPtr[i].ref = Tcl_GetString(oPtr);
} else {
dataPtr[i].ref = "";
}
}
}
baseSortDataPtr = dataPtr;
switch (infoPtr->sortOrder) {
case SORT_NONE:
for (i=0; i<infoPtr->number; i++) {
p[i] = i;
}
break;
case SORT_THREADED:
for (i=0; i<infoPtr->number; i++) {
p[i] = i;
}
qsort((void*)p, infoPtr->number, sizeof(int),RatFolderSortCompareDate);
/*for (i=0; i<infoPtr->number; i++) {
printf("Msg: %d (really %d)\n", i, p[i]);
printf(" Subj: %s\n", dataPtr[p[i]].subject);
printf("MsgId: <%s>\n", dataPtr[p[i]].msgid);
printf(" Ref: <%s>\n", (dataPtr[p[i]].ref ?
dataPtr[p[i]].ref : "(NULL)"));
}*/
/*
* Start by sorting on hard references
*/
dataPtr[p[0]].prev = -1;
dataPtr[p[0]].next = -1;
dataPtr[p[0]].child = -1;
dataPtr[p[0]].parent = -1;
dataPtr[p[0]].tPtr = NULL;
first = last = p[0];
for (i=1; i<infoPtr->number; i++) {
pi = p[i];
dataPtr[pi].tPtr = NULL;
dataPtr[pi].child = -1;
dataPtr[pi].prev = last;
dataPtr[last].next = pi;
dataPtr[pi].next = -1;
dataPtr[pi].child = -1;
dataPtr[pi].parent = -1;
last = pi;
/* Find any replies to the current message */
for (j=i-1; j >= 0 && *dataPtr[pi].msgid; j--) {
pj = p[j];
if (!strcmp(dataPtr[pj].ref, dataPtr[pi].msgid)
&& -1 == dataPtr[pj].parent) {
/* Here 'pj' is considered a reply to 'pi' */
if (dataPtr[pj].prev != -1) {
dataPtr[dataPtr[pj].prev].next = dataPtr[pj].next;
}
if (dataPtr[pj].next != -1) {
dataPtr[dataPtr[pj].next].prev = dataPtr[pj].prev;
}
if (first == pj) first = dataPtr[pj].next;
if (dataPtr[pi].child != -1) {
dataPtr[dataPtr[pi].child].prev = pj;
dataPtr[pj].next = dataPtr[pi].child;
} else {
dataPtr[pj].next = -1;
}
dataPtr[pi].child = pj;
dataPtr[pj].prev = -1;
dataPtr[pj].parent = pi;
}
}
/* Find message which the current message is a reply to */
for (j=0; j<i
&& -1 == dataPtr[pi].parent
&& *dataPtr[pi].ref; j++) {
pj = p[j];
if (!strcmp(dataPtr[pj].msgid, dataPtr[pi].ref)
&& !IsChild(dataPtr, pj, pi)) {
/* Here 'p' is considered a reply to 'pj' */
if (first == pi) {
if (-1 == dataPtr[first].next) {
first = pj;
} else {
first = dataPtr[first].next;
}
}
if (last == pi) {
if (-1 == dataPtr[last].prev) {
last = pj;
} else {
last = dataPtr[last].prev;
}
}
if (dataPtr[pi].next != -1) {
dataPtr[dataPtr[pi].next].prev = dataPtr[pi].prev;
}
if (dataPtr[pi].prev != -1) {
dataPtr[dataPtr[pi].prev].next = dataPtr[pi].next;
}
if (-1 != dataPtr[pj].child) {
for (pj = dataPtr[pj].child; -1 != dataPtr[pj].next;
pj = dataPtr[pj].next);
dataPtr[pi].prev = pj;
dataPtr[pj].next = pi;
} else {
dataPtr[pj].child = pi;
dataPtr[pi].prev = -1;
}
dataPtr[pi].parent = pj;
dataPtr[pi].next = -1;
break;
}
}
}
/*
* Now we have a number of 'trees' linked by hard references.
* Here we try to link the top nodes in all trees by subject.
*/
for (i=1; i<infoPtr->number; i++) {
pi = p[i];
if (-1 != dataPtr[pi].parent) continue;
for (j=i-1; j>=0; j--) {
pj = p[j];
if (!strcmp(dataPtr[pi].subject, dataPtr[pj].subject)
&& !IsChild(dataPtr, pj, pi)) {
/*
* 'pi' is later in the same thread as 'pj'
* First we remove it from the list.
*/
dataPtr[dataPtr[pi].prev].next = dataPtr[pi].next;
if (dataPtr[pi].next != -1) {
dataPtr[dataPtr[pi].next].prev = dataPtr[pi].prev;
}
/*
* If the parent to 'pj' also has the same subject
* then we add this message after 'pj', otherwise
* we add it under 'pj'
*/
for (k=pj; -1 != dataPtr[k].prev; k = dataPtr[k].prev);
if (-1 != dataPtr[k].parent && !strcmp(dataPtr[pi].subject,
dataPtr[dataPtr[k].parent].subject)) {
dataPtr[pi].prev = pj;
dataPtr[pi].next = dataPtr[pj].next;
if (dataPtr[pj].next != -1) {
dataPtr[dataPtr[pj].next].prev = pi;
}
dataPtr[pj].next = pi;
} else {
dataPtr[pi].parent = pj;
dataPtr[pi].prev = -1;
dataPtr[pi].next = dataPtr[pj].child;
if (-1 != dataPtr[pj].child) {
dataPtr[dataPtr[pj].child].prev = pi;
dataPtr[dataPtr[pj].child].parent = -1;
}
dataPtr[pj].child = pi;
}
break;
}
}
}
/*printf("First: %d\n", first);
for (i=0; i<infoPtr->number; i++) {
printf("%d:\tprev: %2d next: %2d parent: %2d child: %2d\n",
i, dataPtr[i].prev, dataPtr[i].next, dataPtr[i].parent,
dataPtr[i].child);
}*/
RatFolderSortLinearize(p, infoPtr->number, dataPtr, first, 0);
for (i=0; i<infoPtr->number; i++) {
(*infoPtr->setInfoProc)(interp,(ClientData)infoPtr,
RAT_FOLDER_THREADING, i, dataPtr[i].tPtr);
}
break;
case SORT_SUBJDATE:
case SORT_SENDERDATE:
/*
* This algorithm is complicated:
* - First we build a list of unique subjects in uniqList. Each entry
* in this list contains the index of the first message with this
* subject. The messages are linked with the nextPtr field in
* the SortData structs.
* - Then we sort each found subject. This is done by placing the
* indexes of the messages in subList. And sort that. When it
* is sorted we rebuild the subject chains via the nextPtr;
* - After that we sort the first message in each subject. This is done
* by reusing the uniqList. We replace each entry in it with a
* pointer to the first entry in the set. Actually we do this in
* the preceding step. Then we sort this list.
* - Finally we build to result array.
*/
uniqList = (int*)ckalloc(2*infoPtr->number*sizeof(*uniqList));
subList = &uniqList[infoPtr->number];
lengthList = &uniqList[2*infoPtr->number];
for (i=uniqListUsed=0; i<infoPtr->number; i++) {
for (j=0; j<uniqListUsed; j++) {
if (infoPtr->sortOrder == SORT_SUBJDATE ?
!strcmp(dataPtr[i].subject,
dataPtr[uniqList[j]].subject) : !strcmp(
dataPtr[i].sender, dataPtr[uniqList[j]].sender)) {
dataPtr[i].nextPtr = dataPtr[uniqList[j]].nextPtr;
dataPtr[uniqList[j]].nextPtr = &dataPtr[i];
break;
}
}
if (j == uniqListUsed) {
dataPtr[i].nextPtr = NULL;
uniqList[uniqListUsed++] = i;
}
}
for (i=0; i<uniqListUsed; i++) {
if (NULL != dataPtr[uniqList[i]].nextPtr) {
for (j = 0, dPtr = &dataPtr[uniqList[i]]; dPtr;
dPtr = dPtr->nextPtr) {
subList[j++] = dPtr-dataPtr;
}
qsort((void*)subList, j, sizeof(int),RatFolderSortCompareDate);
for (k=0; k<j-1; k++) {
dataPtr[subList[k]].nextPtr = &dataPtr[subList[k+1]];
}
dataPtr[subList[k]].nextPtr = NULL;
uniqList[i] = subList[0];
}
}
qsort((void*)uniqList, uniqListUsed, sizeof(int),
RatFolderSortCompareDate);
for (i=k=0; i<uniqListUsed; i++) {
for (dPtr = &dataPtr[uniqList[i]]; dPtr; dPtr = dPtr->nextPtr) {
p[k++] = dPtr-baseSortDataPtr;
}
}
ckfree(uniqList);
break;
case SORT_SENDER:
for (i=0; i<infoPtr->number; i++) {
p[i] = i;
}
qsort((void*)p,infoPtr->number,sizeof(int),RatFolderSortCompareSender);
break;
case SORT_SUBJECT:
for (i=0; i<infoPtr->number; i++) {
p[i] = i;
}
qsort((void*)p, infoPtr->number, sizeof(int),
RatFolderSortCompareSubject);
break;
case SORT_DATE:
for (i=0; i<infoPtr->number; i++) {
p[i] = i;
}
qsort((void*)p, infoPtr->number, sizeof(int),RatFolderSortCompareDate);
break;
case SORT_SIZE:
for (i=0; i<infoPtr->number; i++) {
p[i] = i;
}
qsort((void*)p, infoPtr->number, sizeof(int),RatFolderSortCompareSize);
}
if (infoPtr->reverse) {
tmpPtr = (int*)ckalloc(infoPtr->number*sizeof(int));
for (i=infoPtr->number-1, j=0; i >= 0; i--) {
if (!infoPtr->hidden[p[i]]) {
tmpPtr[j++] = p[i];
}
}
memcpy(p, tmpPtr, j*sizeof(int));
ckfree(tmpPtr);
} else {
for (i=j=0; i < infoPtr->number; i++) {
if (!infoPtr->hidden[p[i]]) {
p[j++] = p[i];
}
}
}
infoPtr->visible = j;
/*
* Cleanup dataPtr
*/
for (i=0; i<infoPtr->number; i++) {
if (needSender) {
ckfree(dataPtr[i].sender);
}
}
ckfree(dataPtr);
}
/*
*----------------------------------------------------------------------
*
* RatFolderSortCompare* --
*
* This is the comparison functions used by RatFolderSort. They
* expect to get pointers to integers as argumens. The integers
* pointed at are indexes into a list of SortData structs which can be
* found at the address in baseSortDataPtr.
*
* Results:
* An integers describing the order of the compared objects.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
static int
RatFolderSortCompareDate(const void *arg1, const void *arg2)
{
return baseSortDataPtr[*((int*)arg1)].date
- baseSortDataPtr[*((int*)arg2)].date;
}
static int
RatFolderSortCompareSubject(const void *arg1, const void *arg2)
{
return strcmp(baseSortDataPtr[*((int*)arg1)].subject,
baseSortDataPtr[*((int*)arg2)].subject);
}
static int
RatFolderSortCompareSender(const void *arg1, const void *arg2)
{
return strcmp(baseSortDataPtr[*((int*)arg1)].sender,
baseSortDataPtr[*((int*)arg2)].sender);
}
static int
RatFolderSortCompareSize(const void *arg1, const void *arg2)
{
return baseSortDataPtr[*((int*)arg1)].size
- baseSortDataPtr[*((int*)arg2)].size;
}
/*
*----------------------------------------------------------------------
*
* RatFolderCanonalizeSubject --
*
* Copy a subject line and remove certain constructs (the re:).
*
* Results:
* A new object reference
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
Tcl_Obj*
RatFolderCanonalizeSubject (const char *s)
{
const char *e;
Tcl_Obj *nPtr;
int len;
if (s) {
/*
* We first try to find the start of the actual text (i.e. without any
* leading Re:'s and whitespaces. Then we find how long the text is
* (ignore trailing whitespaces).
*/
len = strlen(s);
e = s+len-1;
while (*s) {
while (*s && s < e && isspace((unsigned char)*s)) s++, len--;
if (!strncasecmp(s, "re", 2) && (':' == s[2]
|| isspace((unsigned char)s[2]))) {
s += 2;
len -= 2;
if (*s == ':') {
s++;
len--;
}
} else {
break;
}
}
while (isspace((unsigned char)*e) && e > s) {
e--;
len--;
}
nPtr = Tcl_NewStringObj(s, len);
len = Tcl_UtfToLower(Tcl_GetString(nPtr));
Tcl_SetObjLength(nPtr, len);
return nPtr;
} else {
return Tcl_NewStringObj("", 0);
}
}
/*
*----------------------------------------------------------------------
*
* IsChild --
*
* See if one message has the other as one of its ancestors.
*
* Results:
* A non-zero value if the child is related to the parent
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
static int
IsChild(SortData *dataPtr, int child, int parent)
{
int i;
for (i=child; i > -1 && i != parent;) {
if (-1 != dataPtr[i].parent) {
i = dataPtr[i].parent;
} else {
i = dataPtr[i].prev;
}
}
return (i == parent);
}
/*
*----------------------------------------------------------------------
*
* RatFolderSortLinearize --
*
* Linearizes the linked list of messages. The list is also sorted.
*
* Results:
* Number of elements added to p
*
* Side effects:
* Modifies the p array.
*
*
*----------------------------------------------------------------------
*/
static int
RatFolderSortLinearize(int *p, int n, SortData *dataPtr, int first, int depth)
{
int *s = (int*)ckalloc(sizeof(int)*n);
int i, j, k, o, ns;
char *c;
for (i=first, ns=0; -1 != i; i = dataPtr[i].next) {
s[ns++] = i;
}
qsort((void*)s, ns, sizeof(int), RatFolderSortCompareDate);
for (i=j=0; i<ns; i++) {
if (depth) {
dataPtr[s[i]].tPtr = Tcl_NewObj();
for (k=0; k<depth-1; k++) {
Tcl_AppendToObj(dataPtr[s[i]].tPtr, " ", 1);
}
Tcl_AppendToObj(dataPtr[s[i]].tPtr, "+", 1);
}
p[j++] = s[i];
if (-1 != dataPtr[s[i]].child) {
o = j;
j += RatFolderSortLinearize(&p[j], n-ns, dataPtr,
dataPtr[s[i]].child, depth+1);
if (i < ns-1 && depth) {
while (o<j) {
c = Tcl_GetStringFromObj(dataPtr[p[o++]].tPtr, NULL);
c[depth-1] = '|';
}
}
}
}
ckfree(s);
return j;
}
/*
*----------------------------------------------------------------------
*
* RatGetMsgInfo --
*
* Gets info from message structure and formats it somewhat. None of the
* informations items needs both the messagePtr and the eltPtr. The following
* table describes which needs which:
*
* Info msgPtr envPtr bodyPtr eltPtr size
* RAT_FOLDER_SUBJECT - needed - - -
* RAT_FOLDER_CANONSUBJECT - needed - - -
* RAT_FOLDER_NAME needed needed - - -
* RAT_FOLDER_MAIL_REAL - needed - - -
* RAT_FOLDER_MAIL needed needed - - -
* RAT_FOLDER_NAME_RECIPIENT needed needed - - -
* RAT_FOLDER_MAIL_RECIPIENT needed needed - - -
* RAT_FOLDER_SIZE - - - - needed
* RAT_FOLDER_SIZE_F - - - - needed
* RAT_FOLDER_DATE_F - needed - needed -
* RAT_FOLDER_DATE_N - needed - needed -
* RAT_FOLDER_STATUS [not supported ]
* RAT_FOLDER_TYPE - - needed - -
* RAT_FOLDER_PARAMETERS - - needed - -
* RAT_FOLDER_FLAGS - - - needed -
* RAT_FOLDER_UNIXFLAGS - - - needed -
* RAT_FOLDER_MSGID - needed - - -
* RAT_FOLDER_REF - needed - - -
* RAT_FOLDER_INDEX [not supported ]
* RAT_FOLDER_THREADING [not supported ]
*
* Results:
* A pointer to a string which is valid at least until the next call
* to this structure.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
Tcl_Obj*
RatGetMsgInfo(Tcl_Interp *interp, RatFolderInfoType type, MessageInfo *msgPtr,
ENVELOPE *envPtr, BODY *bodyPtr, MESSAGECACHE *eltPtr, int size)
{
Tcl_Obj *oPtr = NULL, *pPtr[2];
time_t time, zonediff;
MESSAGECACHE dateElt, *dateEltPtr;
PARAMETER *parmPtr;
ADDRESS *adrPtr;
struct tm tm;
char buf[1024], *s;
switch (type) {
case RAT_FOLDER_SUBJECT:
oPtr = Tcl_NewStringObj(
RatDecodeHeader(interp,envPtr->subject,0), -1);
break;
case RAT_FOLDER_CANONSUBJECT:
oPtr = RatFolderCanonalizeSubject(
RatDecodeHeader(interp,envPtr->subject,0));
break;
case RAT_FOLDER_MAIL_REAL:
for (adrPtr = envPtr->from; adrPtr; adrPtr = adrPtr->next) {
if (adrPtr->mailbox && adrPtr->host) {
break;
}
}
if (!adrPtr) {
oPtr = Tcl_NewStringObj(NULL, 0);
} else {
oPtr = Tcl_NewStringObj(RatAddressMail(adrPtr), -1);
}
break;
case RAT_FOLDER_NAME:
if (!envPtr->from) {
oPtr = Tcl_NewObj();
break;
}
if (RAT_ISME_YES == msgPtr->fromMe
|| (RAT_ISME_UNKOWN == msgPtr->fromMe
&& RatAddressIsMe(interp, envPtr->from, 1))) {
msgPtr->fromMe = RAT_ISME_YES;
if (envPtr->to && envPtr->to->personal) {
oPtr = Tcl_GetVar2Ex(interp, "t", "to", TCL_GLOBAL_ONLY);
if (Tcl_IsShared(oPtr)) {
oPtr = Tcl_DuplicateObj(oPtr);
}
Tcl_AppendToObj(oPtr, ": ", 2);
Tcl_AppendToObj(oPtr,
RatDecodeHeader(interp,
envPtr->to->personal, 0),
-1);
break;
}
} else {
msgPtr->fromMe = RAT_ISME_NO;
if (envPtr->from->personal) {
oPtr = Tcl_NewStringObj(RatDecodeHeader(interp,
envPtr->from->personal, 0), -1);
break;
}
}
/* fallthrough */
case RAT_FOLDER_MAIL:
oPtr = Tcl_NewObj();
if (RAT_ISME_YES == msgPtr->fromMe
|| (RAT_ISME_UNKOWN == msgPtr->fromMe
&& RatAddressIsMe(interp, envPtr->from, 1))) {
msgPtr->fromMe = RAT_ISME_YES;
adrPtr = envPtr->to;
Tcl_AppendObjToObj(oPtr, Tcl_GetVar2Ex(interp, "t", "to",
TCL_GLOBAL_ONLY));
Tcl_AppendToObj(oPtr, ": ", 2);
} else {
msgPtr->fromMe = RAT_ISME_NO;
adrPtr = envPtr->from;
}
for (; adrPtr; adrPtr = adrPtr->next) {
if (adrPtr->mailbox && adrPtr->host) {
break;
}
}
if (!adrPtr) {
Tcl_DecrRefCount(oPtr);
oPtr = Tcl_NewObj();
} else {
Tcl_AppendToObj(oPtr, RatAddressMail(adrPtr), -1);
}
break;
case RAT_FOLDER_NAME_RECIPIENT:
if (!envPtr->to) {
oPtr = Tcl_NewObj();
break;
}
msgPtr->toMe = RAT_ISME_NO;
if (envPtr->to->personal) {
oPtr = Tcl_NewStringObj(
RatDecodeHeader(interp, envPtr->to->personal, 0), -1);
break;
}
/* fallthrough */
case RAT_FOLDER_MAIL_RECIPIENT:
oPtr = Tcl_NewObj();
adrPtr = envPtr->to;
for (; adrPtr; adrPtr = adrPtr->next) {
if (adrPtr->mailbox && adrPtr->host) {
break;
}
}
if (!adrPtr) {
Tcl_DecrRefCount(oPtr);
oPtr = Tcl_NewObj();
} else {
Tcl_AppendToObj(oPtr, RatAddressMail(adrPtr), -1);
}
break;
case RAT_FOLDER_SIZE:
oPtr = Tcl_NewIntObj(size);
break;
case RAT_FOLDER_SIZE_F:
oPtr = RatMangleNumber(size);
break;
case RAT_FOLDER_DATE_F:
if (envPtr->date && T == mail_parse_date(&dateElt, envPtr->date)) {
dateEltPtr = &dateElt;
} else {
dateEltPtr = eltPtr;
}
oPtr = RatFormatDate(interp, dateEltPtr->month-1, dateEltPtr->day);
break;
case RAT_FOLDER_DATE_N:
if (envPtr->date && T == mail_parse_date(&dateElt, envPtr->date)) {
dateEltPtr = &dateElt;
} else {
dateEltPtr = eltPtr;
}
tm.tm_sec = dateEltPtr->seconds;
tm.tm_min = dateEltPtr->minutes;
tm.tm_hour = dateEltPtr->hours;
tm.tm_mday = dateEltPtr->day;
tm.tm_mon = dateEltPtr->month - 1;
tm.tm_year = dateEltPtr->year+70;
tm.tm_wday = 0;
tm.tm_yday = 0;
tm.tm_isdst = -1;
time = mktime(&tm);
zonediff = (dateEltPtr->zhours*60+dateEltPtr->zminutes)*60;
if (!dateEltPtr->zoccident) {
zonediff *= -1;
}
time += zonediff;
oPtr = Tcl_NewObj();
Tcl_SetLongObj(oPtr, time);
break;
case RAT_FOLDER_DATE_IMAP4:
if (envPtr->date && T == mail_parse_date(&dateElt, envPtr->date)) {
dateEltPtr = &dateElt;
} else {
dateEltPtr = eltPtr;
}
mail_date(buf, dateEltPtr);
oPtr = Tcl_NewStringObj(buf, -1);
break;
case RAT_FOLDER_TYPE:
oPtr = Tcl_NewObj();
Tcl_AppendStringsToObj(oPtr, body_types[bodyPtr->type], "/",
bodyPtr->subtype, NULL);
break;
case RAT_FOLDER_PARAMETERS:
oPtr = Tcl_NewObj();
for (parmPtr = bodyPtr->parameter; parmPtr;
parmPtr = parmPtr->next) {
pPtr[0] = Tcl_NewStringObj(parmPtr->attribute, -1);
pPtr[1] = Tcl_NewStringObj(parmPtr->value, -1);
Tcl_ListObjAppendElement(interp, oPtr, Tcl_NewListObj(2,pPtr));
}
break;
case RAT_FOLDER_TO:
oPtr = Tcl_NewStringObj("", 0);
Tcl_SetObjLength(oPtr, RatAddressSize(envPtr->to, 1));
Tcl_GetString(oPtr)[0] = '\0';
rfc822_write_address(Tcl_GetString(oPtr), envPtr->to);
Tcl_SetObjLength(oPtr, strlen(Tcl_GetString(oPtr)));
break;
case RAT_FOLDER_FROM:
oPtr = Tcl_NewStringObj("", 0);
Tcl_SetObjLength(oPtr, RatAddressSize(envPtr->from, 1));
Tcl_GetString(oPtr)[0] = '\0';
rfc822_write_address(Tcl_GetString(oPtr), envPtr->from);
Tcl_SetObjLength(oPtr, strlen(Tcl_GetString(oPtr)));
break;
case RAT_FOLDER_SENDER:
oPtr = Tcl_NewStringObj("", 0);
Tcl_SetObjLength(oPtr, RatAddressSize(envPtr->sender, 1));
Tcl_GetString(oPtr)[0] = '\0';
rfc822_write_address(Tcl_GetString(oPtr), envPtr->sender);
Tcl_SetObjLength(oPtr, strlen(Tcl_GetString(oPtr)));
break;
case RAT_FOLDER_CC:
oPtr = Tcl_NewStringObj("", 0);
Tcl_SetObjLength(oPtr, RatAddressSize(envPtr->cc, 1));
Tcl_GetString(oPtr)[0] = '\0';
rfc822_write_address(Tcl_GetString(oPtr), envPtr->cc);
Tcl_SetObjLength(oPtr, strlen(Tcl_GetString(oPtr)));
break;
case RAT_FOLDER_REPLY_TO:
oPtr = Tcl_NewStringObj("", 0);
Tcl_SetObjLength(oPtr, RatAddressSize(envPtr->reply_to, 1));
Tcl_GetString(oPtr)[0] = '\0';
rfc822_write_address(Tcl_GetString(oPtr), envPtr->reply_to);
Tcl_SetObjLength(oPtr, strlen(Tcl_GetString(oPtr)));
break;
case RAT_FOLDER_FLAGS:
oPtr = Tcl_NewStringObj(MsgFlags(eltPtr), -1);
break;
case RAT_FOLDER_UNIXFLAGS:
s = buf;
if (eltPtr->seen) *s++ = 'R';
if (eltPtr->deleted) *s++ = 'D';
if (eltPtr->flagged) *s++ = 'F';
if (eltPtr->answered) *s++ = 'A';
oPtr = Tcl_NewStringObj(buf, s-buf);
break;
case RAT_FOLDER_MSGID:
oPtr = RatExtractRef(envPtr->message_id);
if (NULL == oPtr) {
oPtr = Tcl_NewObj();
}
break;
case RAT_FOLDER_REF:
oPtr = RatExtractRef(envPtr->in_reply_to);
if (NULL == oPtr) {
oPtr = RatExtractRef(envPtr->references);
}
if (NULL == oPtr) {
oPtr = Tcl_NewObj();
}
break;
case RAT_FOLDER_STATUS: /*fallthrough */
case RAT_FOLDER_INDEX: /*fallthrough */
case RAT_FOLDER_THREADING: /*fallthrough */
case RAT_FOLDER_END:
oPtr = Tcl_NewObj();
break;
}
msgPtr->info[type] = oPtr;
Tcl_IncrRefCount(oPtr);
return oPtr;
}
/*
*----------------------------------------------------------------------
*
* MsgFlags --
*
* Returns the flags of a message
*
* Results:
* A poiter to a static area containing the flags
*
* Side effects:
* None
*
*
*----------------------------------------------------------------------
*/
char*
MsgFlags(MESSAGECACHE *eltPtr)
{
static Tcl_DString ds;
static int initialized = 0;
if (!initialized) {
Tcl_DStringInit(&ds);
initialized = 1;
}
Tcl_DStringSetLength(&ds, 0);
if (eltPtr->seen) {
Tcl_DStringAppend(&ds, flag_name[RAT_SEEN].imap_name, -1);
}
if (eltPtr->deleted) {
if (Tcl_DStringLength(&ds)) {
Tcl_DStringAppend(&ds, " ",1);
}
Tcl_DStringAppend(&ds, flag_name[RAT_DELETED].imap_name, -1);
}
if (eltPtr->flagged) {
if (Tcl_DStringLength(&ds)) {
Tcl_DStringAppend(&ds, " ",1);
}
Tcl_DStringAppend(&ds, flag_name[RAT_FLAGGED].imap_name, -1);
}
if (eltPtr->answered) {
if (Tcl_DStringLength(&ds)) {
Tcl_DStringAppend(&ds, " ",1);
}
Tcl_DStringAppend(&ds, flag_name[RAT_ANSWERED].imap_name, -1);
}
if (eltPtr->draft) {
if (Tcl_DStringLength(&ds)) {
Tcl_DStringAppend(&ds, " ",1);
}
Tcl_DStringAppend(&ds, flag_name[RAT_DRAFT].imap_name, -1);
}
if (eltPtr->recent) {
if (Tcl_DStringLength(&ds)) {
Tcl_DStringAppend(&ds, " ",1);
}
Tcl_DStringAppend(&ds, flag_name[RAT_RECENT].imap_name, -1);
}
return Tcl_DStringValue(&ds);
}
/*
*----------------------------------------------------------------------
*
* RatParseFrom --
*
* Parse the time in a 'From ' line. See ../imap/src/osdep/unix/unix.h
* for details on how this line may look like.
*
* Results:
* A poiter to a static area containing a MESSAGECACHE.
* The only valid fields in this are the time-fields
*
* Side effects:
* None
*
*
*----------------------------------------------------------------------
*/
MESSAGECACHE*
RatParseFrom(const char *from)
{
static MESSAGECACHE elt;
const char *cPtr;
int i=0, found;
/*
* Start by finding the weekday name, if it is followed by one
* space and a month-spec, then we assume we have found the date.
*/
for (cPtr = from+5, found=0; cPtr && !found; cPtr = strchr(cPtr, ' ')) {
for (i=0; i<7 && strncmp(cPtr+1, dayName[i], 3); i++);
if (i < 7) {
for (i=0; i<12; i++) {
if (!strncmp(cPtr+5, monthName[i], 3)) {
found = 1;
break;
}
}
}
}
if (!found) {
return NULL;
}
elt.month = i+1;
for (cPtr+=8; isspace(*cPtr) && *cPtr; cPtr++);
if (!*cPtr) return NULL;
elt.day = atoi(cPtr);
for (cPtr++; !isspace(*cPtr) && *cPtr; cPtr++);
for (cPtr++; isspace(*cPtr) && *cPtr; cPtr++);
if (!*cPtr) return NULL;
elt.hours = atoi(cPtr);
for (cPtr++; ':' != *cPtr && *cPtr; cPtr++);
elt.minutes = atoi(cPtr+1);
for (cPtr++; isdigit(*cPtr) && *cPtr; cPtr++);
if (!*cPtr) return NULL;
if (':' == *cPtr) {
elt.seconds = atoi(cPtr+1);
for (cPtr++; isdigit(*cPtr) && *cPtr; cPtr++);
} else {
elt.seconds = 0;
}
while (1) {
for (cPtr++; isspace(*cPtr) && *cPtr; cPtr++);
if (isdigit(cPtr[0]) && isdigit(cPtr[1])
&& isdigit(cPtr[2]) && isdigit(cPtr[3])){
elt.year = atoi(cPtr)-BASEYEAR;
break;
} else {
for (cPtr++; !isspace(*cPtr) && *cPtr; cPtr++);
}
if (!*cPtr) return NULL;
}
elt.zoccident = 0;
elt.zhours = 0;
elt.zminutes = 0;
return &elt;
}
/*
*----------------------------------------------------------------------
*
* RatUpdateFolder --
*
* Updates a folder
*
* Results:
* The number of new messages is left in the tcl result-buffer.
* A standard tcl-result is returned.
*
* Side effects:
* None
*
*
*----------------------------------------------------------------------
*/
int
RatUpdateFolder(Tcl_Interp *interp, RatFolderInfo *infoPtr, RatUpdateType mode)
{
int i, numNew, oldNumber, oldVisible, delta;
oldVisible = infoPtr->visible;
oldNumber = infoPtr->number;
numNew = (*infoPtr->updateProc)(infoPtr, interp, mode);
if (numNew < 0) {
return TCL_ERROR;
} else if (numNew
|| oldNumber != infoPtr->number
|| infoPtr->sortOrderChanged) {
if (infoPtr->number > infoPtr->allocated) {
infoPtr->allocated = infoPtr->number;
infoPtr->msgCmdPtr = (char **) ckrealloc(infoPtr->msgCmdPtr,
infoPtr->allocated*sizeof(char*));
infoPtr->privatePtr = (ClientData**)ckrealloc(infoPtr->privatePtr,
infoPtr->allocated*sizeof(ClientData*));
infoPtr->presentationOrder = (int *) ckrealloc(
infoPtr->presentationOrder,
infoPtr->allocated*sizeof(int));
infoPtr->hidden = (int *) ckrealloc(infoPtr->hidden,
infoPtr->allocated*sizeof(int));
}
for (i=infoPtr->number-numNew; i<infoPtr->number; i++) {
infoPtr->msgCmdPtr[i] = (char *) NULL;
infoPtr->privatePtr[i] = (ClientData*) NULL;
(*infoPtr->initProc)(infoPtr, interp, i);
}
RatFolderSort(interp, infoPtr);
infoPtr->sortOrderChanged = 0;
}
delta = infoPtr->visible - oldVisible;
Tcl_SetObjResult(interp, Tcl_NewIntObj((delta>0 ? delta : 0)));
if (delta) {
Tcl_SetVar2Ex(interp, "folderExists", infoPtr->cmdName,
Tcl_NewIntObj(infoPtr->visible), TCL_GLOBAL_ONLY);
Tcl_SetVar2Ex(interp, "folderRecent", infoPtr->cmdName,
Tcl_NewIntObj(infoPtr->recent), TCL_GLOBAL_ONLY);
Tcl_SetVar2Ex(interp, "folderUnseen", infoPtr->cmdName,
Tcl_NewIntObj(infoPtr->unseen), TCL_GLOBAL_ONLY);
Tcl_SetVar2Ex(interp, "folderChanged", infoPtr->cmdName,
Tcl_NewIntObj(++folderChangeId), TCL_GLOBAL_ONLY);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* RatFolderUpdateTime --
*
* Updates a folder
*
* Results:
* The number of new messages is left in the tcl result-buffer.
* A standard tcl-result is returned.
*
* Side effects:
* None
*
*
*----------------------------------------------------------------------
*/
static void
RatFolderUpdateTime(ClientData clientData)
{
RatFolderInfo *infoPtr = (RatFolderInfo*)clientData;
RatSetBusy(timerInterp);
if (TCL_OK == RatUpdateFolder(timerInterp, infoPtr, RAT_UPDATE)) {
infoPtr->timerToken = Tcl_CreateTimerHandler(
infoPtr->watcherInterval*1000, RatFolderUpdateTime,
(ClientData)infoPtr);
} else {
char *err = cpystr(Tcl_GetStringResult(timerInterp));
RatLog(timerInterp, RAT_ERROR, err, RATLOG_EXPLICIT);
ckfree(err);
}
RatClearBusy(timerInterp);
}
/*
*----------------------------------------------------------------------
*
* RatFolderClose --
*
* Closes a folder
*
* Results:
* A standard tcl result
*
* Side effects:
* Many, all associated with cleaning up from the folder
*
*
*----------------------------------------------------------------------
*/
int
RatFolderClose(Tcl_Interp *interp, RatFolderInfo *infoPtr, int force)
{
RatFolderInfo **rfiPtrPtr;
int i, ret, expunge;
Tcl_Obj *oPtr;
oPtr = Tcl_GetVar2Ex(interp, "option", "expunge_on_close",TCL_GLOBAL_ONLY);
Tcl_GetBooleanFromObj(interp, oPtr, &expunge);
if (infoPtr->refCount-- != 1 && !force) {
if (expunge) {
RatUpdateFolder(interp, infoPtr, RAT_SYNC);
}
return TCL_OK;
}
for (rfiPtrPtr = &ratFolderList; infoPtr != *rfiPtrPtr;
rfiPtrPtr = &(*rfiPtrPtr)->nextPtr);
*rfiPtrPtr = infoPtr->nextPtr;
ckfree(infoPtr->name);
ckfree(infoPtr->definition);
ret = (*infoPtr->closeProc)(infoPtr, interp, expunge);
for(i=0; i < infoPtr->number; i++) {
if (NULL != infoPtr->msgCmdPtr[i]) {
(void)RatMessageDelete(interp, infoPtr->msgCmdPtr[i]);
infoPtr->msgCmdPtr[i] = 0;
}
}
if (infoPtr->watcherInterval) {
Tcl_DeleteTimerHandler(infoPtr->timerToken);
}
Tcl_UnsetVar2(interp, "folderExists", infoPtr->cmdName,TCL_GLOBAL_ONLY);
Tcl_UnsetVar2(interp, "folderUnseen", infoPtr->cmdName,TCL_GLOBAL_ONLY);
Tcl_UnsetVar2(interp, "folderChanged", infoPtr->cmdName,TCL_GLOBAL_ONLY);
Tcl_UnsetVar2(interp, "vFolderWatch", infoPtr->cmdName,TCL_GLOBAL_ONLY);
Tcl_UnsetVar(interp, infoPtr->cmdName, TCL_GLOBAL_ONLY);
(void)Tcl_DeleteCommand(interp, infoPtr->cmdName);
ckfree(infoPtr->cmdName);
ckfree(infoPtr->msgCmdPtr);
ckfree(infoPtr->privatePtr);
ckfree(infoPtr->presentationOrder);
ckfree(infoPtr->hidden);
ckfree(infoPtr);
return ret;
}
/*
*----------------------------------------------------------------------
*
* RatFolderInsert --
*
* Insert messages into a folder
*
* Results:
* A standard tcl result
*
* Side effects:
* Messages gets added
*
*
*----------------------------------------------------------------------
*/
int
RatFolderInsert(Tcl_Interp *interp, RatFolderInfo *infoPtr,int num,char **msgs)
{
int result;
result = (*infoPtr->insertProc)(infoPtr, interp, num, msgs);
RatUpdateFolder(interp, infoPtr, RAT_UPDATE);
return result;
}
/*
*----------------------------------------------------------------------
*
* RatGetFolderSpec --
*
* Return the mailbox spec for the given folder definition
*
* Results:
* A pointer to a static area of memeory where the spec is stored.
* This area will be overwritten by the next call.
*
* Side effects:
* None
*
*
*----------------------------------------------------------------------
*/
char*
RatGetFolderSpec(Tcl_Interp *interp, Tcl_Obj *def)
{
static Tcl_DString ds, tmpDS;
static int initialized = 0;
Tcl_Obj *oPtr, **objv, **fobjv, **mobjv, **sobjv;
int objc, fobjc, mobjc, sobjc, port, i, j;
char buf[64], *type, *file, *c;
if (0 == initialized) {
Tcl_DStringInit(&ds);
} else {
Tcl_DStringSetLength(&ds, 0);
}
Tcl_ListObjGetElements(interp, def, &objc, &objv);
if (objc < 4) {
return NULL;
}
type = Tcl_GetString(objv[1]);
if (!strcmp(type, "file")) {
file = Tcl_TranslateFileName(interp, Tcl_GetString(objv[3]), &tmpDS);
if (NULL == file) {
Tcl_DStringAppend(&ds, "invalid_file_specified", -1);
} else {
RatDecodeQP(file);
Tcl_DStringAppend(&ds, file, -1);
Tcl_DStringFree(&tmpDS);
c = Tcl_GetString(objv[3]);
if ('/' == c[strlen(c)-1]) {
Tcl_DStringAppend(&ds, "/", 1);
}
}
} else if (!strcmp(type, "mh")) {
Tcl_DStringAppend(&ds, "#mh/", 4);
file = cpystr(Tcl_GetString(objv[3]));
RatDecodeQP(file);
Tcl_DStringAppend(&ds, file, -1);
ckfree(file);
} else if (!strcmp(type, "dbase")) {
if (objc < 6) {
return NULL;
}
Tcl_DStringAppend(&ds, Tcl_GetString(objv[3]), -1);
Tcl_DStringAppend(&ds, Tcl_GetString(objv[4]), -1);
Tcl_DStringAppend(&ds, Tcl_GetString(objv[5]), -1);
} else if (!strcmp(type, "imap")
|| !strcmp(type, "pop3")
|| !strcmp(type, "dis")) {
oPtr = Tcl_GetVar2Ex(interp, "mailServer", Tcl_GetString(objv[3]),
TCL_GLOBAL_ONLY);
if (!oPtr) {
return NULL;
}
Tcl_ListObjGetElements(interp, oPtr, &mobjc, &mobjv);
Tcl_DStringAppend(&ds, "{", 1);
Tcl_DStringAppend(&ds, Tcl_GetString(mobjv[0]),
Tcl_GetCharLength(mobjv[0]));
if (TCL_OK == Tcl_GetIntFromObj(interp, mobjv[1], &port) && port!=0) {
snprintf(buf, sizeof(buf), ":%d", port);
Tcl_DStringAppend(&ds, buf, -1);
}
if (!strcmp(type, "pop3")) {
Tcl_DStringAppend(&ds, "/pop3", 5);
} else {
Tcl_DStringAppend(&ds, "/imap", 5);
}
Tcl_ListObjGetElements(interp, mobjv[2], &fobjc, &fobjv);
#ifdef HAVE_OPENSSL
/*
* These flags must be in a specific order to match strings generated
* by c-client. Also only include them if we have SSL available.
*/
for (i=0; cClientFlags[i]; i++) {
for (j=0; j<fobjc; j++) {
if (!strcmp(cClientFlags[i]+1, Tcl_GetString(fobjv[j]))) {
Tcl_DStringAppend(&ds, cClientFlags[i], -1);
break;
}
}
}
#endif /* HAVE_OPENSSL */
for (i=0; i<fobjc; i++) {
Tcl_ListObjGetElements(interp, fobjv[i], &sobjc, &sobjv);
if (2 == sobjc && !strcmp("ssh-cmd", Tcl_GetString(sobjv[0]))) {
tcp_parameters(SET_SSHCOMMAND, Tcl_GetString(sobjv[1]));
}
}
Tcl_DStringAppend(&ds, "/user=\"", 7);
Tcl_DStringAppend(&ds, Tcl_GetString(mobjv[3]),
Tcl_GetCharLength(mobjv[3]));
Tcl_DStringAppend(&ds, "\"", 1);
for (j=0; j<fobjc; j++) {
if (!strcmp("debug", Tcl_GetString(fobjv[j]))) {
Tcl_DStringAppend(&ds, "/debug", 6);
break;
}
}
Tcl_DStringAppend(&ds, "}", 1);
if (strcmp(type, "pop3")) {
file = cpystr(Tcl_GetString(objv[4]));
RatDecodeQP(file);
Tcl_DStringAppend(&ds, file, -1);
ckfree(file);
}
}
return Tcl_DStringValue(&ds);
}
/*
*----------------------------------------------------------------------
*
* RatManageFolder --
*
* See the INTERFACE specification for RatCreateFolder and
* RatDeleteFolder
*
* Results:
* A normal tcl result.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
static int
RatManageFolder(ClientData op, Tcl_Interp *interp, int objc,
Tcl_Obj *CONST objv[])
{
int fobjc;
Tcl_Obj **fobjv;
if (objc != 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
Tcl_GetString(objv[0]), " folderdef\"", (char *) NULL);
return TCL_ERROR;
}
Tcl_ListObjGetElements(interp, objv[1], &fobjc, &fobjv);
if (fobjc < 4) {
Tcl_AppendResult(interp, "Argument \"", Tcl_GetString(objv[1]),
"\" is not a valid vfolderdef.",
(char*)NULL);
return TCL_ERROR;
}
if (!strcmp(Tcl_GetString(fobjv[1]), "dbase")) {
return TCL_OK;
} else {
return RatStdManageFolder(interp, (RatManagementAction)op, objv[1]);
}
}
/*
*----------------------------------------------------------------------
*
* RatFlagNameToInt --
*
* Convert flag name to integer
*
* Results:
* The RatFlag integer
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
static RatFlag
RatFlagNameToInt(const char *name)
{
int f;
for (f=0; flag_name[f].tkrat_name; f++) {
if (!strcmp(name, flag_name[f].tkrat_name)) {
return f;
}
}
/* Not reached unless in error */
return 0;
}
/*
*----------------------------------------------------------------------
*
* RatExtractRef --
*
* Extract and references. This will extract the last message-id
* found in the given text. Also all whitespace ion that message-id
* will be removed.
*
* Results:
* Returns the extracted reference as a tcl object. Or NULL if no
* reference was found.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
Tcl_Obj*
RatExtractRef(CONST84 char *text)
{
CONST84 char *s, *e, *ls, *le;
Tcl_Obj *oPtr;
int quoted = 0;
if (NULL == text) {
return NULL;
}
le = s = text;
ls = NULL;
while (s && *s
&& (s = RatFindCharInHeader(le, '<'))
&& (e = RatFindCharInHeader(s+1, '>'))) {
ls = s+1;
le = e;
}
if (ls) {
oPtr = Tcl_NewObj();
for (s=ls; s<le; s++) {
if ('\\' == *s) {
Tcl_AppendToObj(oPtr, ++s, 1);
} else if ('"' == *s) {
if (quoted) {
quoted = 0;
} else {
quoted = 1;
}
} else {
Tcl_AppendToObj(oPtr, s, 1);
}
}
return oPtr;
} else {
return NULL;
}
}
/*
*----------------------------------------------------------------------
*
* RatGetIdentDef --
*
* Get the identifying part of a definition. This part is used
* to locate previously open instances of the same folder.
*
* Results:
* Returns a pointer to a static buffer which contains the def.
* The buffer will be valid until the next call.
*
* Side effects:
* None.
*
*
*----------------------------------------------------------------------
*/
static char*
RatGetIdentDef(Tcl_Interp *interp, Tcl_Obj *defPtr)
{
static Tcl_DString ds;
static int initialized = 0;
Tcl_Obj **objv;
int i, objc;
if (!initialized) {
Tcl_DStringInit(&ds);
} else {
Tcl_DStringSetLength(&ds, 0);
}
Tcl_ListObjGetElements(interp, defPtr, &objc, &objv);
Tcl_DStringAppendElement(&ds, Tcl_GetString(objv[1]));
for (i=3; i<objc; i++) {
Tcl_DStringAppendElement(&ds, Tcl_GetString(objv[i]));
}
return Tcl_DStringValue(&ds);
}
syntax highlighted by Code2HTML, v. 0.9.1