/* Copyright (c) 2017, Computer History Museum 
All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to 
the limitations in the disclaimer below) provided that the following conditions are met: 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
   disclaimer in the documentation and/or other materials provided with the distribution. 
 * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products 
   derived from this software without specific prior written permission. 
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE 
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE. */

/* Copyright (c) 1998 by QUALCOMM Incorporated */
#define FILE_NUM 113

/**********************************************************************
 *	imapmailboxes.c
 *
 *		This file contains the functions necessary to keep track of the
 *	remote IMAP mailboxes.  Everything defined here is designed to work
 *	in the main thread only.
 **********************************************************************/

#include "imapmailboxes.h"

#pragma segment IMAP

#define IMAP_TYPE 'IMAP'
#define BOX_INFO_TYPE 'imap'
#define BOX_NAME_TYPE 'inam'
#define BOX_FLAGS_TYPE 'ifag'
#define IMAP_ID 128

// mailbox error codes
enum
{
	IMAPParamErr=1,
	IMAPTreeInUse
};

MailboxNodeHandle GetIMAPMailboxLevel(IMAPStreamPtr imapStream, const char *ref, Boolean includeInbox, Boolean progress);
OSErr UpdateLocalCacheMailboxes(MailboxNodeHandle *tree, FSSpecPtr inSpec, Boolean progress);
Boolean CreateIMAPCacheMailbox(FSSpecPtr spec, Boolean folder);
OSErr CleanIMAPFolder(void);
OSErr CleanCacheFolder(FSSpecPtr folderToClean, MailboxNodeHandle treeToClean);
OSErr ReadIMAPMailboxInfo(FSSpecPtr spec, MailboxNodeHandle node);
OSErr RebuildIMAPMailboxTree(FSSpecPtr dir, PersHandle pers, MailboxNodeHandle *tree, Boolean *bHasQueuedCommands);
MailboxNodeHandle RebuildPersIMAPMailboxTree(PersHandle pers);
Boolean StrCmpNotIncludingEndingDelimiter(UPtr str1, UPtr str2, char delimiter);
void LocateNodeByVDInAllPersTrees(short vRef, long dirId, MailboxNodeHandle *node, PersHandle *pers);
MailboxNodeHandle LocateNodeByVD(MailboxNodeHandle tree, short vRef, long dirId);
MailboxNodeHandle LocateParentNode(MailboxNodeHandle tree, MailboxNodeHandle child);
MailboxNodeHandle LocateParentNodeInChildren(MailboxNodeHandle tree, MailboxNodeHandle child);
OSErr BuildIMAPMailboxName(MailboxNodeHandle parent, FSSpecPtr newSpec, Boolean folder, CStr *newMailboxName);
Boolean DeleteIMAPMailboxNode(IMAPStreamPtr imapStream, MailboxNodeHandle node);
Boolean FetchDelimiter(IMAPStreamPtr imapStream, MailboxNodeHandle node);
void TransferLocalTreeInfo(MailboxNodeHandle oldTree, MailboxNodeHandle newTree);
char *NewIMAPMailboxName(MailboxNodeHandle node, UPtr name, char *newName);
MailboxNodeHandle LocateSpecialMailbox(MailboxNodeHandle tree, long mboxAtt);
Boolean ChooseSpecialMailbox(PersHandle pers, short msg, FSSpecPtr specialSpec);
OSErr EnsureIMAPCacheFolders(FSSpecPtr attach, FSSpecPtr imapStubSpec);
void AttachOpenTocsToIMAPMailboxTrees(void);
MailboxNodeHandle CreateSpecialMailbox(Boolean tryCreate, long mboxAtt);
#define SpecialMailboxIsTrash(a) (a == LATT_TRASH)
#define SpecialMailboxIsJunk(a)	(a == LATT_JUNK)
Boolean IMAPMailboxExists(Str255 mailboxName);
Boolean GetNextMailboxToExpunge(MailboxNodeHandle tree, FSSpec *spec);
Boolean MarkOrExpungeMailboxIfNeeded(MailboxNodeHandle mbox, FSSpec *spec, Boolean bNow);
void CloseIMAPMailboxImmediately(TOCHandle tocH, Boolean bHiddenToo);
OSErr CleanHiddenCacheMailbox(TOCHandle hidTocH);
			
/**********************************************************************
 * CreateIMAPMailFolder - make the folder to store the IMAP cache in
 **********************************************************************/
void CreateIMAPMailFolder(void)
{
	FSSpec spec;
	Str63 folder;
	long junk;
	OSErr err;
	Boolean isFolder, boolJunk;
	
	// create the IMAP Folder
	err=FSMakeFSSpec(Root.vRef,Root.dirId,GetRString(folder,IMAP_MAIL_FOLDER_NAME),&spec);
	if (err==fnfErr) err = FSpDirCreate(&spec,smSystemScript,&junk);
	
	if (!err)
	{
		err = ResolveAliasFile(&spec,True,&isFolder,&boolJunk);
		if (!err && !folder) err = fnfErr;
	}
	if (err) DieWithError(MAIL_FOLDER,err);
	
	IMAPMailRoot.vRef = spec.vRefNum;
	IMAPMailRoot.dirId = SpecDirId(&spec);
	
	// clean up the IMAP Cache folder, removing all unused personality caches
	CleanIMAPFolder();
}


/**********************************************************************
 *	AddMailbox - this is called once per mailbox when receiving status
 *		stuff from the server.  Add the mailbox to the current pers
 *		IMAPmailboxtree.
 **********************************************************************/
void AddMailbox(MAILSTREAM *mailStream, char *name, char delimiter, long attributes)
{	
	MailboxNodeHandle mbox = 0;
	char *mailboxName = 0;
	Str255 scratch, inbox;
	Str255 progressMessage;
	static long lastProgress = 0;
	
	GetRString(scratch, IMAP_INBOX_NAME);
	PtoCcpy(inbox, scratch);
	
	// if this mailbox is the same as the ref, then ignore it.  Some servers do this.  bxxxxxxs.
	if (mailStream->fRef && !StrCmpNotIncludingEndingDelimiter(name,(char *)(mailStream->fRef),delimiter)) return;
	
	// Make sure we're not adding a duplicate INBOX
	if (*name && (strlen(name) == strlen(inbox)) && !striscmp(name,inbox))
	{
		if (mailStream->fIncludeInbox) mailStream->fIncludeInbox = false;
		else return;	//INBOX has already been added.
	}

	// display some periodic progress, once a secod
	if (mailStream->fProgress)
	{
		if ((TickCount() - lastProgress) > 30)
		{
			PathToMailboxName(name, progressMessage, delimiter);			
			PROGRESS_MESSAGE(kpMessage,progressMessage);
			lastProgress = TickCount();
		}
	}
	
	// create a new node for the mailbox list
	mbox = NewZH(MailboxNode);
	
	if (mbox)
	{
		// Should we strip delimiters from the mailbox name?
		mailboxName = cpystr(name);	
		
		(*mbox)->mailboxName = mailboxName;
		(*mbox)->delimiter = delimiter;
		(*mbox)->attributes = attributes;
		(*mbox)->uidValidity = 0;	// this is the default uid_validity.  It's bogus.
		(*mbox)->messageCount = 0;	// we also don't know how many messages are in this mailbox until we SELECT it.
		(*mbox)->next = (*mbox)->childList = 0;
		(*mbox)->queuedFlags = 0;
		(*mbox)->flagLock = false;
		(*mbox)->persId = (*CurPersSafe)->persId;
			
		// add this mailbox to the list.
		LL_Queue(mailStream->fListResultsHandle,mbox,(MailboxNodeHandle));
	}
	else WarnUser(MEM_ERR,MemError());
	
}

/**********************************************************************
 *	GetIMAPMailboxes - get a list of mailboxes on the server pointed
 *		to by the specifiec personality
 **********************************************************************/
Boolean GetIMAPMailboxes(IMAPStreamPtr imapStream, Boolean progress)
{	
	Str255  ref, scratch;
	MailboxNodeHandle root = nil;
	
	// Create a new mailStream structure within imapStream.  Our results will end up in there.
	if (imapStream->mailStream == nil)
	{
		mail_new_stream(&imapStream->mailStream, imapStream->pServerName, &(imapStream->portNumber), NULL);
		if (!imapStream->mailStream) return (false);
	}
	
	// kill the old imapStream->mailStream->fListResultsHandle
	if (imapStream->mailStream->fListResultsHandle) 
	{
		LDRef(imapStream->mailStream->fListResultsHandle);
		DisposeMailboxTree(&(imapStream->mailStream->fListResultsHandle));
		UL(imapStream->mailStream->fListResultsHandle);
	}
	// create the first node.  This will be a root node pointing to the cache folder.
	root = NewZH(MailboxNode);
	
	if (!root) WarnUser(MEM_ERR,MemError());
	else 
	{
		// set up the root node ...
		(*root)->attributes = LATT_ROOT;
		(*root)->persId = (*CurPers)->persId;
		
		// go fetch the mailbox list
		imapStream->mailStream->fListResultsHandle = GetIMAPMailboxLevel(imapStream, PtoCcpy(ref, GetRString(scratch,IMAP_MAILBOX_LOCATION_PREFIX)), !PrefIsSet(PREF_IMAP_SKIP_TOP_LEVEL_INBOX), progress);
		
		// if successful, push the root node into the queue ...
		if (imapStream->mailStream->fListResultsHandle) LL_Push(imapStream->mailStream->fListResultsHandle,root);
		// otherwise, throw away the root node we made.
		else ZapMailboxNode(&root);
	}
	
	// don't return anything if command period was pressed ...
	if (CommandPeriod && imapStream->mailStream->fListResultsHandle) 
	{
		LDRef(imapStream->mailStream->fListResultsHandle);
		DisposeMailboxTree(&(imapStream->mailStream->fListResultsHandle));
		UL(imapStream->mailStream->fListResultsHandle);
	}
	
	// get the mailbox hierarchy
	return (imapStream->mailStream->fListResultsHandle!=0);
}

/**********************************************************************
 *	GetIMAPMailboxLevel - list all the child mailboxes of ref
 **********************************************************************/
MailboxNodeHandle GetIMAPMailboxLevel(IMAPStreamPtr imapStream, const char *ref, Boolean includeInbox, Boolean progress)
{	
	MailboxNodeHandle thisLevel = 0;
	MailboxNodeHandle node = 0;
	
	// Remember the ref.  Some servers like to return it as a child mailbox.  bxxxxxxs.
	imapStream->mailStream->fRef = ref;
	imapStream->mailStream->fIncludeInbox = includeInbox;
	imapStream->mailStream->fProgress = progress;
	
	// Get a list of all the mailboxes at this level
	if (IMAPListUnSubscribed(imapStream, ref, includeInbox))
	{
		thisLevel = imapStream->mailStream->fListResultsHandle;
		imapStream->mailStream->fListResultsHandle = 0;
		
		// go through this level and see who has child mailboxes
		for (node=thisLevel; node; node = (*node)->next)
			if (((*node)->mailboxName && *((*node)->mailboxName))		//make sure this node has a name
				&& !((*node)->attributes & LATT_NOINFERIORS)			//and this mailbox can have children
				&& !((*node)->attributes & LATT_HASNOCHILDREN))			//and the server thinks it does
			{
				char childRef[NETMAXMBX];
				short nameLen = strlen((*node)->mailboxName);
				
				// Put the delimiter at the end of the mailbox name
				
				strcpy(childRef, (*node)->mailboxName);
				childRef[nameLen] = (*node)->delimiter;
				childRef[nameLen+1] = NULL;

				LDRef(node);
				(*node)->childList = GetIMAPMailboxLevel(imapStream, childRef, false, progress);
				UL(node);
			}
	}
	return(thisLevel);
}

/**********************************************************************
 *	CreateNewPersCaches - create pers caches for new personalities.
 **********************************************************************/
OSErr CreateNewPersCaches(void)
{	
	OSErr err = noErr;
	IMAPStreamPtr iStream = 0;
	PersHandle oldPers = CurPers;
	Str255 cacheName;
	FSSpec spec;
				
	for (CurPers=PersList;CurPers && (err==noErr);CurPers=(*CurPers)->next)
	{
		// if this is an IMAP personality ...
		if (IsIMAPPers(CurPers))
		{
			// see if the personality already has a cache
		 	PersNameToCacheName(CurPers, cacheName);
		 	
		 	err = FSMakeFSSpec(IMAPMailRoot.vRef,IMAPMailRoot.dirId,cacheName,&spec);	
		 	if (err == fnfErr)  	// it doesn't.
		 	{
		 		// create a cache folder for this personality
		 		if (!CreateIMAPCacheMailbox(&spec, true)) 
		 		{
					// rebuild the mailbox menus to reflect the change.
					BuildBoxMenus();
					MBTickle(nil,nil);
					err = noErr;
				}
		 	}
		 }
	}
		
	CurPers = oldPers;
	return (err);
}

/**********************************************************************
 *	UpdateLocalCache - make sure the local cache belonging to the 
 *		current personality jives with what's on the remote server
 **********************************************************************/
 OSErr UpdateLocalCache(Boolean progress)
 {
 	OSErr err = noErr;
 	FSSpec spec, itemSpec;
 	Str63 cacheName;
 	Str63 name;
 	long junk;
 	 	
 	// pick the name for the cache
 	PersNameToCacheName(CurPers, cacheName);
 	
 	// see if the personality already has a cache
 	err = FSMakeFSSpec(IMAPMailRoot.vRef,IMAPMailRoot.dirId,cacheName,&spec);	
 	if (err == fnfErr)  	// create it if we need to
 	{
 		if (!CreateIMAPCacheMailbox(&spec, true)) err = noErr;
 	}
 	else
		spec.parID = SpecDirId(&spec);
	 
	// see if the personality has an attachment folder.	
 	if (err == noErr)
 	{
		err=FSMakeFSSpec(spec.vRefNum,spec.parID,GetRString(name,IMAP_ATTACH_FOLDER),&itemSpec);
		if (err==fnfErr)
		{
		 	err = FSpDirCreate(&itemSpec,smSystemScript,&junk);
		}
	}
		
	// now go update this personality's cache mailboxes.
 	if (err == noErr)
 	{
 		LDRef(CurPers);
 		err = UpdateLocalCacheMailboxes(&(*CurPers)->mailboxTree, &spec, progress);
 		UL(CurPers);
 	}

	// make sure the first node in the mailboxtree points inside the cache folder itself.
	LDRef(CurPers);
	SimpleMakeFSSpec(spec.vRefNum,spec.parID,cacheName,&((*(*CurPers)->mailboxTree)->mailboxSpec));
	UL(CurPers);
	
	// rebuild the mailbox menus to reflect the change.
	BuildBoxMenus();
	MBTickle(nil,nil);	
	
	// Update all open TOCs so they point to valid IMAP mailbox nodes
	AttachOpenTocsToIMAPMailboxTrees();
	
 	return (err);
 }
 
 
 /**********************************************************************
 *	UpdateLocalCacheMailboxes - create the mailboxes in the cache of
 *		the current personalty
 **********************************************************************/
OSErr UpdateLocalCacheMailboxes(MailboxNodeHandle *tree, FSSpecPtr inSpec, Boolean progress)
{
	OSErr err = noErr;
	MailboxNodeHandle node, scan;
	FSSpec spec;
	Str63 mName;	// this is the mailbox's actual name
	Str255 pInbox, cInbox;
	Str255 progressMessage;
	static long lastProgress = 0;
		
	GetRString(pInbox, IMAP_INBOX_NAME);
	PtoCcpy(cInbox, pInbox);

	err = CleanCacheFolder(inSpec, *tree);
	
	// create the IMAP cache mailboxes			
	node = *tree;	
	while ((node!=nil) && (err==noErr))
	{
		if ((*node)->mailboxName)
		{
			// figure out the mailbox name.  The node contains the full pathname
			LDRef(node);
			PathToMailboxName((*node)->mailboxName, mName, (*node)->delimiter);
			UL(node);
				
			// create the folder if we have to
			err = FSMakeFSSpec(inSpec->vRefNum, inSpec->parID, mName, &spec);
	
			// display some periodic progress if we're going to create a new mailbox
			if (progress && err)
			{
				if ((TickCount() - lastProgress) > 30)
				{
					ComposeRString(progressMessage,IMAP_CACHE_CREATE,mName);
					PROGRESS_MESSAGE(kpMessage,progressMessage);
					lastProgress = TickCount();
				}
			}
			
			//
			// special case: if the user has specified a location prefix, we may end up with two Inboxes in the root level.
			// so, if the name of this mailbox is Inbox, the pathname is not "Inbox", and it's in the root level, then
			// create a second INBOX with a unique name.
			//
			
			// if a folder with that name was found ...
			if ((err == noErr) && AFSpIsItAFolder(&spec))
			{
				// and the name of the mailbox is inbox ...
				if (StringSame(pInbox, mName))	
				{
					LDRef(node);
					
					// but it's not in the root directory on the server ...
					if (striscmp(cInbox, (*node)->mailboxName))
					{
						// and it's in the top level of mailboxes locally ...
						scan = (*CurPers)->mailboxTree;
						while (scan)
						{
							if (scan == node) break;
							else scan = (*scan)->next;
						}
						
						// and there IS another (real) inbox ...
						if (scan && LocateInboxForPers(CurPers))
						{
							// ... then give this second inbox a unique name
							UniqueSpec(&spec, MAX_BOX_NAME);
							PCopy(mName, spec.name);
							err = fnfErr;	// and create it.
						}
					}
					
					UL(node);
				}
			}
							
			// we found something, but it's not a folder
			if ((err == noErr) && (!AFSpIsItAFolder(&spec)))
			{
				// then try again with a unique name
				if ((err=UniqueSpec(&spec, MAX_BOX_NAME))==noErr)
				{
					PCopy(mName, spec.name);
					err = FSMakeFSSpec(inSpec->vRefNum, inSpec->parID, mName, &spec);
				}
			}
			
			if (err != noErr)
			{
				// create a folder for this mailbox
				if (err == fnfErr)
				{
					if (!CreateIMAPCacheMailbox(&spec, true))
					 	err = noErr;
				}
				//else
					//stop processing this level.
			}
			else
			{	
				// Look inside the folder since it already exists
				if ((spec.parID = SpecDirId(&spec))!=0)
				{
					// Remove from this folder all things that aren't on the server
					LDRef(node);
					err = CleanCacheFolder(&spec, (*node)->childList);
					UL(node);
				}
			}
			
			// create the mailbox file.  It has the same name as the folder
			if ((err == noErr))
			{
				//
				//	Note - here we could look at ((*node)->attributes & LATT_NOSELECT) and
				//	create a mailbox file only for SELECTable mailboxes.  I choose to create
				//	a mailbox file and not use it to simplify other things.
				//
				 
				LDRef(node);
				err = FSMakeFSSpec(spec.vRefNum,spec.parID,mName,&((*node)->mailboxSpec));
				if (err == fnfErr)
					if (!CreateIMAPCacheMailbox(&((*node)->mailboxSpec), false)) 
						err = noErr;
				UL(node);

				//update the mailbox imap information
				if (err == noErr) 
				{
					LDRef(node);
					err = WriteIMAPMailboxInfo(&((*node)->mailboxSpec),node);
					UL(node);
				}
			}
			
			// now do this mailbox's children
			if ((err==noErr) && ((*node)->childList)) 
			{
				LDRef(node);
				err = UpdateLocalCacheMailboxes(&((*node)->childList), &spec, progress);
				UL(node);
			}
		}
		node = (*node)->next;
	}

	return (err);
}


/**********************************************************************
 * CreateIMAPCacheMailbox - create a mailbox or folder
 **********************************************************************/
Boolean CreateIMAPCacheMailbox(FSSpecPtr spec, Boolean folder)
{
	int err;
	char *cp;
	long newDirId;
	
	if (*spec->name>MAX_BOX_NAME)
	{
		TooLong(spec->name);
		return(True);
	}
	
	// change all ':'s to something nicer
	for (cp=spec->name+*spec->name;cp>spec->name;cp--)
		if (*cp==':') *cp = '-';
	
	if (folder)
	{
		if (BoxMapCount>MAX_BOX_LEVELS)
		{
			WarnUser(TOO_MANY_LEVELS,MAX_BOX_LEVELS);
			return(True);
		}

		if (err=FSpDirCreate(spec,smSystemScript,&newDirId))
		{
			FileSystemError(CREATING_MAILBOX,spec->name,err);
			return(True);
		}
		spec->parID = newDirId;
	}
	else	// create a mailbox
	{
		err = FSpCreate(spec,CREATOR,IMAP_MAILBOX_TYPE,smSystemScript);
		if (err)
		{
			FileSystemError(CREATING_MAILBOX,spec->name,err);
			return(True);
		}
	}
	return(False);
}

/**********************************************************************
 * CleanIMAPFolder - loop through personality caches, and remove unused
 *	cache folders.
 **********************************************************************/
OSErr CleanIMAPFolder(void)
{
	OSErr err = noErr;
	CInfoPBRec hfi;
	Str63 name;
	Str63 cacheName;
	Boolean current = false;
	FSSpec toDelete;
	Str63 IMAPAttachFolderName;
	
	// only wipe outdated IMAP caches if we have multiple personalities ...
	if (!HasFeature(featureMultiplePersonalities)) return (noErr);
	
	GetRString(IMAPAttachFolderName,IMAP_ATTACH_FOLDER);
	
	PushPers(nil);
	
	hfi.hFileInfo.ioNamePtr = name;
	hfi.hFileInfo.ioFDirIndex = 0;
	while (!DirIterate(IMAPMailRoot.vRef,IMAPMailRoot.dirId,&hfi))
	{
		err = noErr;		//clear the error condition
		current = false;
		// compare the name of the file we just found to the list of personalities.
		for (CurPers=PersList;CurPers && (err==noErr);CurPers=(*CurPers)->next)
		{
			PersNameToCacheName(CurPers, cacheName);
			
			// if this item has the same name as a personality or the attach folder, save it.
			if (StringSame(cacheName, name))
			{
				current = true;
				break;
			}
		}
		if (!current)
		{			
			if ((err=FSMakeFSSpec(IMAPMailRoot.vRef, IMAPMailRoot.dirId, name, &toDelete))==noErr)
			{
				if ((err=RemoveIMAPCacheDir(toDelete))==noErr) hfi.hFileInfo.ioFDirIndex--;	
			}
		}	
	}
	
	PopPers();
	
	return (err);
}


/**********************************************************************
 * CleanCacheFolder - loop through a folder, trash what doesn't belong.
 *	Do this for the current personality only.
 **********************************************************************/
OSErr CleanCacheFolder(FSSpecPtr folderToClean, MailboxNodeHandle treeToClean)
{
	OSErr err = noErr;
	CInfoPBRec hfi;
	Str63 name;
	Str63 cacheName;
	Boolean current = false;
	FSSpec toDelete;
	MailboxNodeHandle node;
	
	//make sure we have a folder to clean AND a tree to clean.
#ifdef	DEBUG
	ASSERT(!(folderToClean->parID == Root.dirId));
#endif
	
	hfi.hFileInfo.ioNamePtr = name;
	hfi.hFileInfo.ioFDirIndex = 0;
	while (!DirIterate(folderToClean->vRefNum,folderToClean->parID,&hfi))
	{
		current = false;
		// compare the name of the file we just found to the mailboxes in the tree to clean
		for (node=treeToClean;node && (err==noErr);node=(*node)->next)
		{
			if (node)
			{
				LDRef(node);
				PathToMailboxName ((*node)->mailboxName, cacheName, (*node)->delimiter);
				UL(node);
				
				if (StringSame(cacheName, name) || IsSpecialIMAPName(name, NULL))
				{
					current = true;
					break;
				}
			}
		}
		if (!current && (hfi.hFileInfo.ioFlAttrib & ioDirMask))	// this folder wasn't found to belong
		{			
			if ((err=FSMakeFSSpec(folderToClean->vRefNum, folderToClean->parID, name, &toDelete))==noErr)
			{
				if ((err=RemoveIMAPCacheDir(toDelete))==noErr)
				{
					hfi.hFileInfo.ioFDirIndex--;	
				}
			}
		}
		// leave stray items alone.  User gets what he deserves by screwing around in here.
	}	
	return (err);
}


/**********************************************************************
 * RemoveIMAPCacheDir - iterate through an IMAP cache folder, trashing
 *	everything.
 **********************************************************************/
OSErr RemoveIMAPCacheDir(FSSpec toDelete)
{
	OSErr err = noErr;
	CInfoPBRec hfi;
	Str63 name;
	FSSpec child;
	long targetDir = SpecDirId(&toDelete);
	
	if (targetDir)
	{
		hfi.hFileInfo.ioNamePtr = name;
		hfi.hFileInfo.ioFDirIndex = 0;
		while (!DirIterate(toDelete.vRefNum, targetDir, &hfi) && err==noErr)
		{		
			// Process subdirectories
			if (hfi.hFileInfo.ioFlAttrib & ioDirMask)
			{	
				SimpleMakeFSSpec(toDelete.vRefNum, targetDir, name, &child);
				err = RemoveIMAPCacheDir(child);
				if (!err) hfi.hFileInfo.ioFDirIndex--;	//	Don't skip next entry
			}
		}
		
		if (err==noErr)	// delete the folder itself
		{
			err = RemoveDir(&toDelete);
			if (err) err = FSpTrash(&toDelete);
		}
	}
			
	return (err);
}


/**********************************************************************
 * PathToMailboxName - return the mailbox name, given a pathname
 **********************************************************************/
void PathToMailboxName (CStr path, Str63 mboxName, char delimiter)
{
	char *scan;
	Str255 pInbox;
	
	// initialize mboxName
	WriteZero(mboxName, sizeof(Str63));
		
	// must have been given a path to convert
	if (!path || (*path==nil)) return;
	
	if (delimiter)
	{
		scan = path + strlen(path) - 1;
		if (*scan == delimiter) scan--;	// if the name ENDS with a delimiter, ignore it.
		while ((scan>path) && (*scan != delimiter)) scan--;
		if (*scan == delimiter) scan++;	// reached a delimiter
	}
	else scan = path;
	
	// convert the good bit to a PString.
	mboxName[0] = MIN(strlen(scan), MAX_BOX_NAME);
	BlockMoveData(scan,&mboxName[1],mboxName[0]);

	// make sure the PString name doesn't end with the delimiter
	if (mboxName[mboxName[0]] == delimiter) mboxName[0] -= 1;
	
	// clean up the name.  Turn all :'s into something else:
	for (scan=mboxName+mboxName[0];scan>mboxName;scan--)
		if (*scan==':') *scan = '-';
				
	// Special case - use prettier INBOX name
	GetRString(pInbox, IMAP_INBOX_NAME);
	if (StringSame(pInbox, scan)) PCopy(mboxName, pInbox);
}
		
/***************************************************************************
 * IsSpecialIMAPName - is this the name of a special file, IMAPly speaking?
 ***************************************************************************/		
Boolean IsSpecialIMAPName(unsigned char *name, Boolean *bIsDir)
{
	Boolean isSpecial = false;
	
	// is this the IMAP Attachments folder?
	isSpecial = EqualStrRes(name,IMAP_ATTACH_FOLDER);
	if (bIsDir) *bIsDir = isSpecial;
	
	
	// is this the cache file for hidden messages?
	if (!isSpecial)
		isSpecial = EqualStrRes(name, IMAP_HIDDEN_TOC_NAME);
	
	return (isSpecial);
}

/************************************************************************
 * IsIMAPSubPers - does this spec point to an IMAP sub personality?
 ************************************************************************/
Boolean IsIMAPSubPers(FSSpecPtr spec)
{
	Boolean result = false;
	Str63 compareName;

	// is this spec in the root level of the IMAP cache folder?
	if (spec->parID==IMAPMailRoot.dirId && SameVRef(spec->vRefNum,IMAPMailRoot.vRef))
	{
		// does it have a name other than the name of the dominant personality?
		
		GetRString(compareName,DOMINANT);
		result = !StringSame(spec->name,compareName);
	}
	
	return (result);
}

/***************************************************************************
 * PersNameToCacheName - given a personality, figure out the name of its
 *	imap cache folder.
 ***************************************************************************/
void PersNameToCacheName(PersHandle pers, UPtr cacheName)
{
	unsigned char *spot;
	SignedByte state = HGetState((Handle)pers);
	
	LDRef(pers);
	PCopy(cacheName, (*pers)->name);
	for (spot=cacheName+1;spot<=cacheName+cacheName[0];spot++)
		if (*spot==':') *spot = '-';
	HSetState((Handle)pers, state);
}

/***************************************************************************
 * WriteIMAPMailboxInfo - write the IMAP mailbox info from node to the mbox
 ***************************************************************************/
OSErr WriteIMAPMailboxInfo(FSSpecPtr spec, MailboxNodeHandle node)
{
	OSErr err = noErr;
	short refN = -1;
	long count = 0;
	short oldResFile = CurResFile();
	Handle resource = 0;
		
	// create a resource fork if we have to
	FSpCreateResFile(spec,CREATOR,IMAP_MAILBOX_TYPE,smSystemScript);

	// open the mailbox file for writing
	if ((refN=FSpOpenResFile(spec,fsRdWrPerm)) != -1)
	{
		LockMailboxNodeHandle(node);
		UseResFile(refN);
		
		// Zap the old info resource
		SetResLoad(False);
		resource = Get1Resource(BOX_INFO_TYPE,IMAP_ID);
		SetResLoad(True);
		if (resource)
		{
			RemoveResource(resource);
			ZapHandle(resource);
		}

		// Zap the old name resource
		SetResLoad(False);
		resource = Get1Resource(BOX_NAME_TYPE,IMAP_ID);
		SetResLoad(True);
		if (resource)
		{
			RemoveResource(resource);
			ZapHandle(resource);
		}

		// add the info to the resource
		resource = DupHandle((Handle)node);
		if (resource)
		{
			AddResource(resource,BOX_INFO_TYPE,IMAP_ID,(*node)->mailboxSpec.name);
			if ((err=ResError())==noErr)
			{
				WriteResource(resource);
				err = ResError();
			}
		}
		
		// Zap the old flags resource
		SetResLoad(False);
		resource = Get1Resource(BOX_FLAGS_TYPE,IMAP_ID);
		SetResLoad(True);
		if (resource)
		{
			RemoveResource(resource);
			ZapHandle(resource);
		}

		// add the flag info to the resource
		if ((*node)->queuedFlags && GetHandleSize((*node)->queuedFlags))
		{
			
			resource = DupHandle((Handle)((*node)->queuedFlags));
			if (resource)
			{
				AddResource(resource,BOX_FLAGS_TYPE,IMAP_ID,(*node)->mailboxSpec.name);
				if ((err=ResError())==noErr)
				{
					WriteResource(resource);
					err = ResError();
				}
			}
		}
					
		//Add the name to the resource as well
		resource = NuHandle(0);
		if (resource && (err=MemError())==noErr)
		{
			err = PtrPlusHand((*node)->mailboxName,resource,strlen((*node)->mailboxName)+1);
			if (err==noErr)
			{
				AddResource(resource,BOX_NAME_TYPE,IMAP_ID,(*node)->mailboxSpec.name);
				if ((err=ResError())==noErr)
				{
					WriteResource(resource);
					err = ResError();
				}

			}
		}
		CloseResFile(refN);
		UnlockMailboxNodeHandle(node);
	}
	else err = ResError();
	
	UseResFile(oldResFile);
	return(err);
}

/***************************************************************************
 * UpdateIMAPMailboxInfo - update the IMAP mailbox info in a cache file
 ***************************************************************************/
OSErr UpdateIMAPMailboxInfo(TOCHandle tocH)
{
	OSErr err = noErr;
	MailboxNodeHandle node = nil;
	FSSpec cacheSpec;
	
	// must be an IMAP mailbox
	if (!(*tocH)->imapTOC) return (noErr);
	
	cacheSpec = ((*tocH)->mailbox.spec);
	
	// find the mailbox node
	node = TOCToMbox(tocH);
	if (node)
	{
		// save the info
		err = WriteIMAPMailboxInfo(&cacheSpec, node);
	}
	
	return (err);
}


/***************************************************************************
 * RebuildAllIMAPMailboxTrees - go through the personalities and rebuild
 *	the imap mailbox trees
 ***************************************************************************/
void RebuildAllIMAPMailboxTrees(void)
{
	OSErr err = noErr;
	PersHandle oldPers = CurPers;
	
	for (CurPers=PersList;CurPers && (err==noErr);CurPers=(*CurPers)->next)
	{
		// build the IMAP tree from the files in the IMAP Folder.
		(*CurPers)->mailboxTree = RebuildPersIMAPMailboxTree(CurPers);
	}
	CurPers = oldPers;
	
	// Update all open TOCs so they point to valid IMAP mailbox nodes
	AttachOpenTocsToIMAPMailboxTrees();
}

/***************************************************************************
 * RebuildPersIMAPMailboxTree - given a personality, rebuild it's mailbox
 *	tree by reading through it's IMAP cache folder
 ***************************************************************************/
MailboxNodeHandle RebuildPersIMAPMailboxTree(PersHandle pers)
{
	MailboxNodeHandle tree = NewZH(MailboxNode);
	OSErr err = noErr;
	Str63 cacheName;
	Boolean bHasQueuedCommands = false;
	
	// make sure we got our first node.
	if (tree==nil) 
		WarnUser(MEM_ERR,MemError());
	else 
	{
		(*tree)->attributes = LATT_ROOT;
		(*tree)->persId = (*pers)->persId;
	}
	
	// Kill the cache in LocateNodeBySpecInAllPersTrees
	(void) LocateNodeBySpecInAllPersTrees(nil,nil,nil);
	
	PersNameToCacheName(pers, cacheName);
	
	// locate this personality's IMAP cache directory
	LDRef(tree);
	err = FSMakeFSSpec(IMAPMailRoot.vRef, IMAPMailRoot.dirId, cacheName, &(*tree)->mailboxSpec);
	if (err == noErr)
	{
		// make sure the first node in the mailboxtree points inside the cache folder itself.
		(*tree)->mailboxSpec.parID = SpecDirId(&((*tree)->mailboxSpec));
			
		// rebuild the tree by reading through the cache.
		err = RebuildIMAPMailboxTree(&((*tree)->mailboxSpec), pers, &tree, &bHasQueuedCommands);
	}
	UL(tree);
	
	// mark this entire tree if one of its leaves has queued commands waiting
	if (bHasQueuedCommands)
		SetIMAPMailboxNeeds(tree, kNeedsExecCmd, true);
	
	return (err==noErr?tree:NIL);
}

/***************************************************************************
 * RebuildIMAPMailboxTree - given a directory, iterate through it and
 *	build a IMAPMailbox tree.  Give it our best shot, this shouldn't fail
 ***************************************************************************/
OSErr RebuildIMAPMailboxTree(FSSpecPtr dir, PersHandle pers, MailboxNodeHandle *tree, Boolean *bHasQueuedCommands)
{
	OSErr err = noErr;
	CInfoPBRec hfi;
	Str63 name, attachDirName;
	FSSpec mboxFile, mboxFolder;
	MailboxNodeHandle node;

	GetRString(attachDirName,IMAP_ATTACH_FOLDER);
	
	hfi.hFileInfo.ioNamePtr = name;
	hfi.hFileInfo.ioFDirIndex = 0;
	while (!DirIterate(dir->vRefNum,dir->parID,&hfi))
	{	
		if (hfi.hFileInfo.ioFlAttrib & ioDirMask)	// this is a directory
		{			
			if (!StringSame(name,attachDirName))	// and it's not the attachments directory
			{
				// it must be an imap mailbox.  Create an imap node from the file within this folder
				SimpleMakeFSSpec(dir->vRefNum,dir->parID,name,&mboxFolder);
				FSMakeFSSpec(dir->vRefNum,SpecDirId(&mboxFolder),name,&mboxFile);
				if (err == noErr)
				{
					// read in the mailbox info from the mailbox file
					node = NewZH(MailboxNode);
					if (node)
					{
						err = ReadIMAPMailboxInfo(&mboxFile, node);
						if (err == noErr)
						{
							// attach the right personality to it
							(*node)->persId = (*pers)->persId;
							
							// add this node to the tree
							LL_Queue(*tree,node,(MailboxNodeHandle));
							
							// mark the tree if there are queued commands in this mailbox
							if (DoesIMAPMailboxNeed(node, kNeedsExecCmd))
								*bHasQueuedCommands = true;
							
							// and process any subfolders of this mailbox
							LDRef(node);
							RebuildIMAPMailboxTree(&mboxFile, pers, &((*node)->childList), bHasQueuedCommands);
							UL(node);
						}
					}
					else WarnUser(MEM_ERR,MemError());
				}
				// else
					// something wrong with this folder.  Skip it.
			}
		}
		// leave stray items alone.  User gets what he deserves by screwing around in here.
	}	
	return (err);
}

/***************************************************************************
 * ReadIMAPMailboxInfo - read the IMAP mailbox info from spec into the node
 ***************************************************************************/
OSErr ReadIMAPMailboxInfo(FSSpecPtr spec, MailboxNodeHandle node)
{
	OSErr err = noErr;
	short refN = -1;
	short oldResFile = CurResFile();
	Handle resource = 0;
	char *mailboxName = 0;
	
	Zero(**node);
	
	// open the mailbox file for reading
	if ((refN=FSpOpenResFile(spec,fsRdPerm)) != -1)
	{
		// read the full pathname from the mailbox.  Store it in some string somewhere.
		UseResFile(refN);
		if ((resource=Get1Resource(BOX_NAME_TYPE,IMAP_ID))==0) err = resNotFound;
		if ((err==noErr) && ((err=ResError())==noErr) && resource != 0)
		{
			LDRef(resource);
			mailboxName = cpystr(*resource);	// copy the pathname
			UL(resource);
			
			// read in the node from the mailbox.
			if ((resource = Get1Resource(BOX_INFO_TYPE,IMAP_ID))==0) err = resNotFound;
			if ((err==noErr) && ((err=ResError())==noErr) && resource != 0)
			{
				BlockMove(*resource, *node, MIN(GetHandleSize(resource),sizeof(MailboxNode)));
				
				// initialize garbage fields
				(*node)->next = 0;
				(*node)->childList = 0;
				(*node)->queuedFlags = 0;
				(*node)->flagLock = false;
				(*node)->mailboxName = mailboxName;	//the node will point to the mailboxname.
				(*node)->mailboxSpec = *spec;
				(*node)->tocRef = 0;
				
				// read in the queued flag info from the mailbox.  Don't care if it's not there.
				resource = Get1Resource(BOX_FLAGS_TYPE,IMAP_ID);
				if (((ResError())==noErr) && resource != 0)
				{
					DetachResource(resource);
					(*node)->queuedFlags = resource;
				}
			}
		}
		CloseResFile(refN);
	}
	else err = ResError();
	
	UseResFile(oldResFile);
	return(err);
}

/**********************************************************************
 *	CreateLocalCache - Build the local cache of IMAP mailboxes for the
 *		IMAP server pointed to by the current personality.
 **********************************************************************/
OSErr CreateLocalCache(void)
{	
	OSErr err = noErr;
	IMAPStreamPtr iStream = 0;
	Boolean progress = true;
	
	// display some progress
	if (progress)
	{
		PROGRESS_START;
		PROGRESS_MESSAGER(kpTitle,IMAP_CACHE_MESSAGE);
	}
	
	// don't do anything if there's a background thread running.  It may be using the mailbox tree.
	if (((*CurPers)->mailboxTree != nil) && !CanModifyMailboxTrees()) return (IMAPTreeInUse);
	
	// Nothing to do if this isn't an IMAP personality
	if (PrefIsSet(PREF_IS_IMAP))
	{
		// create a new imap stream.
		if (iStream = GetIMAPConnection(UndefinedTask, false))
		{	
			if (progress) PROGRESS_MESSAGER(kpSubTitle,IMAP_MAILBOX_LIST_FETCH_GENERAL);
			
			// get the mailbox tree from the server
			if (GetIMAPMailboxes(iStream, progress))
			{
				// tell the user we're about to create some local mailboxes
				if (progress) PROGRESS_MESSAGER(kpSubTitle,IMAP_CACHE_CREATE_GENERAL);

				// (GetIMAPMailboxes will create the mailStream bit of our stream)
				if (iStream->mailStream->fListResultsHandle != nil)
				{
					LDRef(CurPers);
					// get the uidValidity's out of the old tree, and put them in the new tree.
					TransferLocalTreeInfo((*CurPers)->mailboxTree, iStream->mailStream->fListResultsHandle);
					
					// kill the old tree if there is one
					if ((*CurPers)->mailboxTree) DisposeMailboxTree(&((*CurPers)->mailboxTree));
					UL(CurPers);
					
					// and remember the new one
					(*CurPers)->mailboxTree = iStream->mailStream->fListResultsHandle;
					iStream->mailStream->fListResultsHandle = nil;
				
					// then update the local cache
					if ((*CurPers)->mailboxTree) err = UpdateLocalCache(progress);
				}
			}	
			else
				err = IMAPError(kIMAPList, kIMAPListErr, errIMAPListErr);
					
		  	CleanupConnection(&iStream);
		}
	}
	
	if (progress) PROGRESS_END;
	
	return (err);
}

/**********************************************************************
 * StrCmpNotIncludingEndingDelimiter - compare two strings.
 *	return true if they're the same, not taking the final character
 *	into account if it's the delimiter.
 **********************************************************************/
Boolean StrCmpNotIncludingEndingDelimiter(UPtr str1, UPtr str2, char delimiter)
{
	short len1 = strlen(str1);
	short len2 = strlen(str2);
	
	// don't bother with the compare if one or the other strings is nil.
	if ((len1==0) || (len2==0)) return true;
	
	if (str1[len1-1] == delimiter) len1--;
	if (str2[len2-1] == delimiter) len2--;
	
	if (len1 != len2) return(true);
	
	return (strncmp(str1,str2,MIN(len1,len2)));
}

/**********************************************************************
 * DisposePersIMAPMailboxTrees - loop through the personalities, and
 *	dispose each IMAP mailbox tree.
 **********************************************************************/
void DisposePersIMAPMailboxTrees(void)
{
	PersHandle pers;
	
	for (pers=PersList;pers;pers=(*pers)->next)
	{
		LDRef(pers);
		DisposeMailboxTree(&((*pers)->mailboxTree));
		UL(pers);
	}
}

/**********************************************************************
 *	DisposeMailboxTree - recurse through a tree of mailboxes,
 *		and dispose of all the leaves.
 **********************************************************************/
void DisposeMailboxTree(MailboxNodeHandle *tree)
{		
	MailboxNodeHandle node;
	
	// Kill the cache in LocateNodeBySpecInAllPersTrees
	(void) LocateNodeBySpecInAllPersTrees(nil,nil,nil);
		
	while (node=*tree)
	{
		LL_Remove(*tree,node,(MailboxNodeHandle));
		LDRef(node);
		if ((*node)->childList) DisposeMailboxTree(&((*node)->childList));
		if ((*node)->queuedFlags) ZapHandle((*node)->queuedFlags);
		UL(node);
		ZapMailboxNode(&node);
	}
	*tree = nil;
}

/**********************************************************************
 *	ZapMailboxNode - dispose a mailbox node
 **********************************************************************/
void ZapMailboxNode(MailboxNodeHandle *node)
{
	if ((**node)->mailboxName)
	{
		DisposePtr((**node)->mailboxName); 
		(**node)->mailboxName = 0;
	}
	ZapHandle(*node);
}
		
/***************************************************************************
 * LocateNodeBySpecInAllPersTrees - given an FSSpec, locate the node in
 *	all the personality trees with the same fsspec in them.
 ***************************************************************************/
void LocateNodeBySpecInAllPersTrees(FSSpecPtr spec, MailboxNodeHandle *node, PersHandle *pers)
{
	MailboxNodeHandle tree = nil;
	SignedByte state;
	static MailboxNodeHandle oldNode;
	static FSSpec oldSpec;
	static PersHandle oldPers;

	// cache flush?
	if (!spec)
	{
		Zero(oldSpec);
		oldNode=nil;
		oldPers=nil;
		return;
	}
	
	*node = nil;
	*pers = nil;

	// Is it in the cache?
	if (!InAThread() && SameSpec(spec,&oldSpec))
	{
		*node = oldNode;
		*pers = oldPers;
		return;
	}
	
	for (*pers=PersList;*pers;*pers=(**pers)->next)
	{
		state = HGetState((Handle)(*pers));
		LDRef(*pers);
		if ((**pers)->mailboxTree)
		{
			tree = (**pers)->mailboxTree;
			if (tree)
			{
				if (*node = LocateNodeBySpec((**pers)->mailboxTree, spec)) 
				{
					HSetState((Handle)(*pers), state);	
					break;
				}
			}
		}
		HSetState((Handle)(*pers), state);		
	}
	
	if (!InAThread())
	{
		// cache values
		oldSpec = *spec;
		oldNode = *node;
		oldPers = *pers;
	}
}

/***************************************************************************
 * LocateNodeBySpec - given an FSSpec, locate the node in the tree
 ***************************************************************************/
MailboxNodeHandle LocateNodeBySpec(MailboxNodeHandle tree, FSSpecPtr spec)
{
	MailboxNodeHandle node = nil;
	
	while (tree)
	{
		// is this the node we're looking for?
		LockMailboxNodeHandle(tree);
		if (SameSpec(&((*tree)->mailboxSpec), spec)) 
		{
			node = tree;
			UnlockMailboxNodeHandle(tree);
			break;
		}
		
		// is the node we're looking for one of the children of this node?
		if ((*tree)->childList) node = LocateNodeBySpec(((*tree)->childList), spec);
		UnlockMailboxNodeHandle(tree);
		
		if (node) break;
		
		// otherwise, check the next node.
		tree = (*tree)->next;
	}
	return (node);
}

/***************************************************************************
 * LocateNodeByVDInAllPersTrees - given an vRef and dirID, return the
 *	personality of the mailboxTree the values point to, and the parent
 *	node of where this mbox would end up.
 ***************************************************************************/
void LocateNodeByVDInAllPersTrees(short vRef, long dirId, MailboxNodeHandle *node, PersHandle *pers)
{
	SignedByte state;
	
	*node = nil;
	*pers = nil;
	
	for (*pers=PersList;*pers;*pers=(**pers)->next)
	{
		state = HGetState((Handle)*pers);
		LDRef(*pers);
		if (*node = LocateNodeByVD((**pers)->mailboxTree, vRef, dirId)) break;
		HSetState((Handle)(*pers), state);
	}
}


/***************************************************************************
 * LocateNodeByVD - given an dirID and a vRef, see if this file is
 *  inside an IMAP cache folder.
 ***************************************************************************/
MailboxNodeHandle LocateNodeByVD(MailboxNodeHandle tree, short vRef, long dirId)
{
	MailboxNodeHandle scan = tree;
	MailboxNodeHandle node = nil;
	
	while (scan)
	{
		// is this the node we're looking for?
		if (((*scan)->mailboxSpec.vRefNum==vRef) && ((*scan)->mailboxSpec.parID==dirId)) 
		{
			node = scan;
			break;
		}
		
		// is the node we're looking for one of the children of this node?
		LockMailboxNodeHandle(scan);
		if ((*scan)->childList) node = LocateNodeByVD(((*scan)->childList), vRef, dirId);
		UnlockMailboxNodeHandle(scan);
		if (node)
		{
			// we found a node with the same vRef and dirId.  Stop the search.
		 	break;
		}
		
		// otherwise, check the next node.
		scan = (*scan)->next;
	}
	return (node);
}

/***************************************************************************
 * LocateNodeByMailboxName - given a name, locate the node in the tree
 ***************************************************************************/
MailboxNodeHandle LocateNodeByMailboxName(MailboxNodeHandle tree, char *mailboxName)
{
	MailboxNodeHandle scan = tree;
	MailboxNodeHandle node = nil;
	
	// must have a mailbox name ...
	if (!mailboxName) return (nil);
	
	while (scan)
	{
		// makes no sense to check the root node
		if ((*scan)->mailboxName)
		{
			// is this the node we're looking for?
			if (!strcmp(mailboxName, (*scan)->mailboxName)) 
			{
				node = scan;
				break;
			}
			
			// is the node we're looking for one of the children of this node?
			LockMailboxNodeHandle(scan);
			if ((*scan)->childList) node = LocateNodeByMailboxName(((*scan)->childList), mailboxName);
			UnlockMailboxNodeHandle(scan);
				
			if (node) break;
		}
				
		// otherwise, check the next node.
		scan = (*scan)->next;
	}
	return (node);
}

MailboxNodeHandle LocateParentNodeInChildren(MailboxNodeHandle parent, MailboxNodeHandle child)
{
	MailboxNodeHandle scan = (*parent)->childList;
	MailboxNodeHandle p = nil;
	
	while (scan && !p)
	{
		if (scan == child) p = parent;
		else p = LocateParentNodeInChildren(scan, child);
		
		scan = (*scan)->next;
	}
	
	return (p);
}

/***************************************************************************
 * LocateParentNode - given a mailbox node, figure out it's parent
 ***************************************************************************/
MailboxNodeHandle LocateParentNode(MailboxNodeHandle tree, MailboxNodeHandle child)
{
	MailboxNodeHandle scan = tree;
	MailboxNodeHandle parent = nil;
	
	// first see if the child is a top level mailbox.
	if ((*scan)->attributes&LATT_ROOT)
	{
		while (scan)
		{
			if (scan==child) 
			{
				parent = tree;
				break;
			}
			scan = (*scan)->next;
		}
	}
	
	// otherwise, look through all the nodes' children.
	scan = tree;
	while (scan && !parent)
	{		
		parent = LocateParentNodeInChildren(scan, child);
		scan = (*scan)->next;
	}
	
	return (parent);
}

/***************************************************************************
 * ReadIMAPMailboxAttributes - get the IMAP mailbox attributes
 ***************************************************************************/
OSErr ReadIMAPMailboxAttributes(FSSpecPtr spec, long *attributes)
{
	OSErr err;
	MailboxNodeHandle node;
	PersHandle pers;
	
	// initialize
	err = fnfErr;
	*attributes = 0;
			
	LocateNodeBySpecInAllPersTrees(spec, &node, &pers);
	if (node != NULL)
	{
		*attributes = (*node)->attributes;
		err = noErr;
	}
	return(err);
}

/***************************************************************************
 * ReallyIsIMAPMailbox - get the IMAP mailbox attributes, see if it's IMAP
 *	Reads from disk.
 ***************************************************************************/
Boolean ReallyIsIMAPMailbox(FSSpecPtr spec)
{
	OSErr err = noErr;
	short refN = -1;
	short oldResFile = CurResFile();
	short numResources = 0;
		
	// open the mailbox file for reading
	if ((refN=FSpOpenResFile(spec,fsRdPerm)) != -1)
	{
		// read in the node from the mailbox.
		numResources = Count1Resources(BOX_INFO_TYPE);
		err = ResError();
		CloseResFile(refN);
	}
	else err = ResError();
		
	UseResFile(oldResFile);
	return((err==noErr) && (numResources > 0));
}

/***************************************************************************
 * MailboxAttributes - fill in interesting mailbox attributes.  Return
 *	false if this wasn't an IMAP mailbox at all. 
 *	Reads from the IMAP mailbox tree.
 ***************************************************************************/
Boolean MailboxAttributes(FSSpecPtr spec, IMAPMailboxAttributesPtr attributes)
{
	Boolean result = false;
	MailboxNodeHandle node = nil;
	PersHandle pers = nil;
	
	LocateNodeBySpecInAllPersTrees(spec, &node, &pers);
	
	if (node && !((*node)->attributes&LATT_ROOT))
	{
		result = true;	// this was an IMAP mailbox
		attributes->noSelect = ((*node)->attributes&LATT_NOSELECT);
		attributes->noInferiors = ((*node)->attributes&LATT_NOINFERIORS);
		attributes->marked = ((*node)->attributes&LATT_MARKED);
		attributes->unmarked = ((*node)->attributes&LATT_UNMARKED);
		attributes->hasChildren = (((*node)->childList)!=nil);
		attributes->hasNoChildren = ((*node)->attributes&LATT_HASNOCHILDREN);
		attributes->messageCount = (*node)->messageCount;
		attributes->latt = (*node)->attributes;
	}
	
	return (result);
}

/***************************************************************************
 * IsIMAPMailboxFile - return true if the spec points to an IMAP cache
 *	mailbox file.  That is, an actual local mailbox file.
 *	Reads from IMAP mailbox tree.
 ***************************************************************************/
Boolean IsIMAPMailboxFile(FSSpecPtr spec)
{
	MailboxNodeHandle node = nil;	
	return (IsIMAPMailboxFileLo(spec, &node));
}

/***************************************************************************
 * IsIMAPMailboxFileLo - is this file an IMAP cache mailbox?
 ***************************************************************************/
Boolean IsIMAPMailboxFileLo(FSSpecPtr spec, MailboxNodeHandle *node)
{
	PersHandle pers = nil;
	
	LocateNodeBySpecInAllPersTrees(spec, node, &pers);
	
	if (*node == nil)
	{	
		FSSpec s = *spec;
			
		// this might be a hidden toc file.  Find the mailbox file it belongs to
		*node = GetRealIMAPSpec(*spec, &s);		
	}
	
	return ((*node!=nil) && !((**node)->attributes&LATT_ROOT));
}

/***************************************************************************
 * IsIMAPCacheFolder - return true if the spec points to an IMAP cache folder.
 *	Reads from IMAP mailbox tree.
 ***************************************************************************/
Boolean IsIMAPCacheFolder(FSSpecPtr spec)
{
	FSSpec internalMailboxFileSpec;
	MailboxNodeHandle node = nil;
	PersHandle pers = nil;
	CInfoPBRec cinfo;
	
	// file must exist and be a directory
	if (HGetCatInfo(spec->vRefNum,spec->parID,spec->name,&cinfo) || !HFIIsFolder(&cinfo))
		return false;			
	
	SimpleMakeFSSpec(spec->vRefNum ,cinfo.hFileInfo.ioDirID, spec->name, &internalMailboxFileSpec);
	LocateNodeBySpecInAllPersTrees(&internalMailboxFileSpec, &node, &pers);
	
	return (node!=nil);
}

/***************************************************************************
 * IsIMAPVD - return true if the vRef and dirID indicate an IMAP cache
 *	Reads from IMAP mailbox tree
 ***************************************************************************/
Boolean IsIMAPVD(short vRef, long dirId)
{
	MailboxNodeHandle node = nil;
	PersHandle pers = nil;
	
	LocateNodeByVDInAllPersTrees(vRef, dirId, &node, &pers);
	
	return (node!=nil);
}

/***************************************************************************
 * IMAPAddMailbox - Determine where a new mailbox is going to end up.  If it
 *	is going to be created inside an IMAP cache folder, try to create an 
 *	IMAP mailbox.
 *
 *	Return true if we tried to create an IMAP mailbox.
 ***************************************************************************/
Boolean IMAPAddMailbox(FSSpecPtr spec, Boolean folder, Boolean *success, Boolean silent)
{
	PersHandle pers = nil;
	MailboxNodeHandle node = nil;
	MailboxNodeHandle newNode = nil;
	IMAPStreamPtr imapStream = nil;
	PersHandle oldPers = CurPers;
	char *newMailboxName = 0;
	OSErr err = noErr;
	Boolean createdIMAPMailbox = false;
	Boolean result = false;
	Str255 scratch;
	
	if (success) *success = false;
	
	LocateNodeByVDInAllPersTrees(spec->vRefNum, spec->parID, &node, &pers);
	
	if ((pers != nil) && (node != nil))	
	{
		createdIMAPMailbox = true;

		// must be online to do this
		if (Offline && GoOnline()) return (createdIMAPMailbox);

		// must be able to modify the mailbox tree, too.
		// sit an spin until the mailbox tree can be modified ...
		while (!CanModifyMailboxTrees() && !CommandPeriod)
			CycleBalls();
			
		if (CommandPeriod) return (createdIMAPMailbox);
			
		// Set up to connect to the server
		CurPers = pers;
		if (imapStream = GetIMAPConnection(UndefinedTask, CAN_PROGRESS))
		{
			// we're going to need a delimiter if we're creating a folder or a sub-mailbox.
			if (folder || (*node)->mailboxName)
				if ((*node)->delimiter==0) FetchDelimiter(imapStream, node);
			
			//
			// figure out the name of the new mailbox						
			//
			
			if ((err=BuildIMAPMailboxName(node, spec, folder, &newMailboxName))==noErr)
			{
				//
				// create the mailbox on the server
				//
				
				if (result = CreateIMAPMailbox(imapStream, newMailboxName))
				{
					// fetch the attributes of the new mailbox to see if it exists
					result = FetchMailboxAttributes(imapStream, newMailboxName);
					if (result && (imapStream->mailStream->fListResultsHandle==nil))
					{
						// nothing came back.  Look for the mailbox, but forget about the delimiter.
						Zero(scratch);
						BMD(newMailboxName, scratch, MIN(strlen(newMailboxName)-1, sizeof(Str255)));
						result = FetchMailboxAttributes(imapStream, scratch);
					}
					newNode = imapStream->mailStream->fListResultsHandle;
					imapStream->mailStream->fListResultsHandle = nil;
					
					if (result && newNode)
					{
						//
						// add the mailbox to the local tree.	
						//
						
						// kill the children we got.  Could be unfortunate enough to have someone else adding mailboxes
						// between the time we created our and doing the LIST.	
						LDRef(newNode);				
						if ((*newNode)->next) DisposeMailboxTree(&((*newNode)->next));
						if ((*newNode)->childList) DisposeMailboxTree(&((*newNode)->childList));
						UL(newNode);

						// if this is a top level mailbox, add it to the list of top level mailboxes. 
						if ((*node)->attributes&LATT_ROOT)
						{
							LL_Queue((*CurPers)->mailboxTree,newNode,(MailboxNodeHandle));
						}
						else	// Otherwise, add this mailbox to the parent's childlist
						{
							LL_Queue((*node)->childList,newNode,(MailboxNodeHandle));
						}
						
						//
						// update the local cache
						//
						
						if ((UpdateLocalCache(false)!=noErr) && !silent) IMAPError(kIMAPCreateMailbox,kIMAPCreateMailboxErr,err);

						// make sure the right mailbox is returned.  The cache name may be different than the actual name
						*spec = (*newNode)->mailboxSpec;
						
						// clean up the conneciton we used to create the mailbox
						CleanupConnection(&imapStream);
					}
				}
				else // create mailbox failed, and we were told it did.
				{
					// report the IMAP error
					if (!silent) IMAPError(kIMAPCreateMailbox,kIMAPCreateMailboxErr,errIMAPCreateMailbox);
					ZapPtr(newMailboxName);

				}
			}
			else
				WarnUser(MEM_ERR,err);
		}
		else
			WarnUser(MEM_ERR,err);
	
		// clean up the conneciton we used to create the mailbox
		if (imapStream) CleanupConnection(&imapStream);
				
		CurPers = oldPers;
	}
	// else 
		// this mailbox will end up outside an IMAP cache directory.  Regular POP mailbox.
	
	// tell the caller if the mailbox got created on the server
	if (success) *success = result;
	
	return (createdIMAPMailbox);	
}

/***************************************************************************
 * BuildIMAPMailboxName - given a parent node and a new name, build the new
 *	mailbox's pathname.  Building a C-String.  Probably a way to do this
 *	with a stringutil.c function or something.
 *
 *	Note - this looks up the prefix and adds it if necessary.  Make sure
 *	 CurPers is set right when you call this.
 ***************************************************************************/
OSErr BuildIMAPMailboxName(MailboxNodeHandle parent, FSSpecPtr newSpec, Boolean folder, CStr *newMailboxName)
{ 
	OSErr err = noErr;
	short len = 0;
	Str255 pPrefix;
	
	*newMailboxName = nil;
	
	pPrefix[0] = 0;
	GetRString(pPrefix,IMAP_MAILBOX_LOCATION_PREFIX);
	
	// figure out the name of the new mailbox
	len = ((*parent)->mailboxName ? strlen((*parent)->mailboxName)+1 : pPrefix[0])	// length of pathname + delimiter character
														+ newSpec->name[0]	// length of new name
														+ (folder ? 1 : 0)	// trailing delimiter if this is a folder
														+ 1;				// null
						
	if ((*newMailboxName=NuPtr(len)) != 0)
	{		
		if ((*parent)->mailboxName != 0) 
		{
			// the pathname of the new mailbox
			strcpy(*newMailboxName,(*parent)->mailboxName);
			// add a delimiter
			(*newMailboxName)[strlen((*parent)->mailboxName)] = (*parent)->delimiter;
			// NULL terminate before we do the strncat
			(*newMailboxName)[strlen((*parent)->mailboxName)+1] = 0;
			// the new mailbox name
			strncat(*newMailboxName,(newSpec->name)+1,newSpec->name[0]);
			// if we're creating a folder, add the delimiter to the end of the name
			if (folder) (*newMailboxName)[len-2] = (*parent)->delimiter;
			// NULL terminate just to be safe
			(*newMailboxName)[len-1] = 0;
		}
		else	// no previous pathname
		{
			// prepend the location prefix to the new mailbox name. Assume it ends with a delimiter
			PtoCcpy(*newMailboxName, pPrefix);
			// the name of the new mailbox
			strncat(*newMailboxName,(newSpec->name)+1,newSpec->name[0]);
			// if we're creating a folder, add the delimiter char at the end of the name
			if (folder) (*newMailboxName)[len-2] = (*parent)->delimiter;
			// NULL terminate just to be safe
			(*newMailboxName)[len-1] = 0;
		}
	}
	else err = MemError();
			
	return (err);
}

/***************************************************************************
 * IMAPDeleteMailbox - Delete an IMAP mailbox.
 ***************************************************************************/
Boolean IMAPDeleteMailbox(FSSpecPtr toDelete)
{
	Boolean result = false;
	MailboxNodeHandle node = nil;
	MailboxNodeHandle parent = nil;
	PersHandle pers = nil;
	IMAPStreamPtr imapStream = nil;
	PersHandle oldPers = CurPers;
	OSErr err = noErr;
	
	// must be online to do this
	if (Offline && GoOnline()) return (false);

	// must be able to modify the mailbox tree, too.
	if (!CanModifyMailboxTrees()) return (false);
						
	// see if this is an IMAP cache mailbox we're deleting
	LocateNodeBySpecInAllPersTrees(toDelete, &node, &pers);
	
	if ((pers != nil) && (node != nil))	
	{	
		//
		// delete the mailbox from the server.
		//
		
		CurPers = pers;
		if (imapStream = GetIMAPConnection(UndefinedTask, CAN_PROGRESS))
		{
			result = DeleteIMAPMailboxNode(imapStream,node);
			if (result)
			{
				//
				// close the mailbox and all of its open messages
				//
				
				CloseIMAPMailboxImmediately(FindTOC(toDelete), true);
	
				//
				// remove the mailbox from the mailbox tree	
				//

				// find parent of this mailbox
				parent = LocateParentNode((*CurPers)->mailboxTree,node);
				
				if ((*parent)->attributes&LATT_ROOT)
				{
					// remove this node from the top level mailboxes
					LL_Remove((*parent)->next,node,(MailboxNodeHandle));
				}
				else
				{
					// remove this node from it's parent's childlist.
					LL_Remove((*parent)->childList,node,(MailboxNodeHandle));
				}
				
				// get rid of this node and it's children only.
				(*node)->next = nil;
				DisposeMailboxTree(&node);
				
				//
				// update the local cache
				//
				
				if ((UpdateLocalCache(false)!=noErr)) IMAPError(kIMAPDeleteMailbox,kIMAPDeleteMailboxErr,err);
			}
			else
			{
				IMAPError(kIMAPDeleteMailbox,kIMAPDeleteMailboxErr,errIMAPDeleteMailbox);
			}
							
			// clean up the connection we used to delete the mailbox
			CleanupConnection(&imapStream);
		}
		else
			WarnUser(MEM_ERR,err);

		CurPers = oldPers;	
	}
	
	return (result);
}

/************************************************************************
 * IsIMAPMailboxEmpty - return true if the mailbox is really, truely empty
 ************************************************************************/
Boolean IsIMAPMailboxEmpty(FSSpecPtr mailboxSpec)
{
	MailboxNodeHandle node;
	PersHandle pers;
	
	if (IMAPMailboxMessageCount(mailboxSpec, false) == 0)
	{
		// there are no messages in this mailbox.  Does it have children?
		LocateNodeBySpecInAllPersTrees(mailboxSpec, &node, &pers);
		
		// it's empty unless it has children
		if (node && !(*node)->childList)
			return (true);
	}
	
	// there's something in this mailbox
	return (false);
}

/************************************************************************
 * IMAPMailboxMessageCount - return the number of messages in a mailbox.
 *	Hit the server with an EXAMINE command if we're allowed to.
 ************************************************************************/
long IMAPMailboxMessageCount(FSSpecPtr mailboxSpec, Boolean check)
{
	long messageCount = 0, localMessageCount = 0;
	PersHandle pers = nil;
	MailboxNodeHandle node = nil;
	IMAPStreamPtr imapStream = nil;
	PersHandle oldPers = CurPers;
	OSErr err = noErr;
	long flags = SA_MESSAGES;
	TOCHandle tocH;
		
	// Look in the mailbox for messages
	tocH = TOCBySpec(mailboxSpec);
	if (tocH && ((*tocH)->count)) localMessageCount = (*tocH)->count;
	
	// Return the number of messages in the local mailbox if we don't care about an accurate count
	if (!check) messageCount = localMessageCount;
	
	if (messageCount == 0)
	{
		// Check the remote mailbox for the actual message count
		LocateNodeBySpecInAllPersTrees(mailboxSpec, &node, &pers);
		
		if (node && pers)
		{		
			// the mailbox is not empty if it has children or if it has messages in the local cache
			if (!((*node)->childList) && !((*node)->messageCount))
			{
				CurPers = pers;
				
				// Only do the EXAMINE if the user hasn't told us not to.
				if (!PrefIsSet(PREF_IMAP_NO_EXAMINE_ON_DELETE))
				{
					if (!Offline || check)
					{
						// nor is it empty if the mailbox on the server claims it's not empty
						if (imapStream = GetIMAPConnection(UndefinedTask, CAN_PROGRESS))
						{
							LockMailboxNodeHandle(node);
							// get the number of messages using the STATUS command
							if (FetchMailboxStatus(imapStream, (*node)->mailboxName, flags))
							{
								messageCount = GetSTATUSMessageCount(imapStream);
								messageCount = MAX(messageCount, localMessageCount);	// return the local message count if it's bigger
							}
							UnlockMailboxNodeHandle(node);	
											
							CleanupConnection(&imapStream);;
						}
						else
							WarnUser(MEM_ERR,err);
					}
				}
				
				CurPers = oldPers;			
			}
		}
	}
		
	return (messageCount);
}

/***************************************************************************
 * DeleteIMAPMailboxNode - Given a mailbox node, delete the mailbox and all
 *	it's children from the IMAP server.
 ***************************************************************************/
Boolean DeleteIMAPMailboxNode(IMAPStreamPtr imapStream, MailboxNodeHandle node)
{
	Boolean result = true;
	MailboxNodeHandle scan = nil;
	
	// kill this mailbox's child mailboxes
	if ((*node)->childList)
	{
		scan = (*node)->childList;
		while (scan && result)
		{
			result = DeleteIMAPMailboxNode(imapStream, scan);
			scan = (*scan)->next;
		}
	}
	
	//delete the mailbox itself.
	if (result) 
	{
		LDRef(node);
		result = DeleteIMAPMailbox(imapStream, (*node)->mailboxName);
		UL(node);
	}

	return (result);
}

/***************************************************************************
 * FetchDelimiter - Given an imap connection and a mailbox node, fill in 
 *	the delimiter field of the node with the delimiter char used by the
 *	mailbox.  We'll do a [LIST "" ""]
 ***************************************************************************/
Boolean FetchDelimiter(IMAPStreamPtr imapStream, MailboxNodeHandle node)
{
	Boolean result = false;
	MailboxNodeHandle n = nil;
	
	// Do a LIST "" ""
	LockMailboxNodeHandle(node);
	if (FetchMailboxAttributes(imapStream, nil))
	{
		// FetchMailboxAttributes places its result in the mailStream ...
		n = imapStream->mailStream->fListResultsHandle;
		imapStream->mailStream->fListResultsHandle = nil;
		
		if (n)
		{
			(*node)->delimiter = (*n)->delimiter;
			ZapMailboxNode(&n);
		}
	}
	UnlockMailboxNodeHandle(node);(node);
	return (result);
}

/***************************************************************************
 * IsIMAPCacheName - is this name reserved for an IMAP cache folder?
 *	Reads from PersList
 ***************************************************************************/		
Boolean IsIMAPCacheName(unsigned char *name)
{
	Str63 cacheName;
	Boolean isIMAPName = false;
	PersHandle pers = nil;
	
	for (pers=PersList;pers;pers=(*pers)->next)
	{
		PersNameToCacheName(pers, cacheName);
		if (StringSame(name,cacheName))
		{
			isIMAPName = true;
			break;
		}
	}
	return (isIMAPName);
}

/***************************************************************************
 * IMAPExists - return true if there is at least one personality that
 *	foolishly wishes to do IMAP
 ***************************************************************************/		
Boolean IMAPExists(void)
{
	Boolean exists = false;
	PersHandle oldPers = CurPers;
	
	for (CurPers=PersList;CurPers;CurPers=(*CurPers)->next)
	{
		if (PrefIsSet(PREF_IS_IMAP))
		{
			exists = true;
			break;
		}
	}
	CurPers = oldPers;
	
	return (exists);
}

/***************************************************************************
 * SpecIsFilled - return true if the spec contains something useful
 ***************************************************************************/
Boolean SpecIsFilled(FSSpecPtr spec)
{
	return (spec && (spec->parID!=0) && (spec->name[0]!=0));
}

/***************************************************************************
 * IMAPRefreshAllCaches - refresh all the IMAP caches, connecting to 
 *	each server, fetching the mailbox list, and updating the local mailboxes
 ***************************************************************************/
OSErr IMAPRefreshAllCaches(void)
{
	PersHandle pers;
	
	for (pers = PersList; pers; pers = (*pers)->next)
		(*pers)->imapRefresh = 1;
	
	return (IMAPRefreshPersCaches());
}

/***************************************************************************
 * IMAPRefreshPersCaches - refresh all the IMAP caches or personalities
 *	that need it.
 ***************************************************************************/
OSErr IMAPRefreshPersCaches(void)
{
	OSErr err = noErr;
	PersHandle oldPers = CurPers;
	
	// must be online to do this
	if (Offline && GoOnline()) return (OFFLINE);
	
	// collect passwords for the personalities to be refreshed
	for (CurPers = PersList; CurPers && (err!=userCancelled); CurPers = (*CurPers)->next)
	{
		// only do this for IMAP personalities.
		if (PrefIsSet(PREF_IS_IMAP) && ((*CurPers)->imapRefresh))
		{	
			if (!PrefIsSet(PREF_KERBEROS) && (*CurPers)->password[0] == 0)
			{
				err = PersFillPw(CurPers,0);		
			}
		}
	}	
	
	// do the actual refresh now.
	for (CurPers = PersList; CurPers && (err!=userCanceledErr); CurPers = (*CurPers)->next)
	{
		if (PrefIsSet(PREF_IS_IMAP) && ((*CurPers)->imapRefresh) && (err != userCancelled)) 
		{
			err = CreateLocalCache();
		}
		(*CurPers)->imapRefresh = 0;
			
		// forget the keychain password so it's not written to the settings file
		if (KeychainAvailable() && PrefIsSet(PREF_KEYCHAIN)) Zero((*CurPers)->password);
		
		if (CommandPeriod) err = userCanceledErr;
	}
	
	CurPers = oldPers;
	
	// Update all open TOCs so they point to valid IMAP mailbox nodes
	AttachOpenTocsToIMAPMailboxTrees();
	
	return (err);
}

/**********************************************************************
 * TransferLocalTreeInfo - go through new tree, transfer 
 *	- uidvalidity
 *	- if it's a trash mailbox
 *	- whether the mailbox needs to be filtered or not
 *	to the new tree.  This is data we store only locally.
 **********************************************************************/
void TransferLocalTreeInfo(MailboxNodeHandle oldTree, MailboxNodeHandle newTree)
{
	MailboxNodeHandle newScan = newTree, oldScan = nil;
		
	if (newTree && oldTree)
	{
		while (newScan)
		{
			if ((*newScan)->mailboxName)
			{
				LDRef(newScan);
				if ((oldScan=LocateNodeByMailboxName(oldTree, (*newScan)->mailboxName))!=0)
				{
					(*newScan)->uidValidity = (*oldScan)->uidValidity;
					if (IsIMAPTrashMailbox(oldScan)) 
						(*newScan)->attributes |= LATT_TRASH;
					else if (IsIMAPJunkMailbox(oldScan)) 
						(*newScan)->attributes |= LATT_JUNK;
					if (DoesIMAPMailboxNeed(oldScan, kNeedsFilter)) 
						SetIMAPMailboxNeeds(newScan, kNeedsFilter, true);
					if (DoesIMAPMailboxNeed(oldScan, kNeedsPoll)) 
						SetIMAPMailboxNeeds(newScan, kNeedsPoll, true);
				}
			 	
				// do the children
				TransferLocalTreeInfo(oldTree, (*newScan)->childList);
				UL(newScan);
			}
			
			// next
			newScan = (*newScan)->next;
		}
	}
}

/***************************************************************************
 * LocateInboxForPers - return a handle to the INBOX of a given personality
 ***************************************************************************/
MailboxNodeHandle LocateInboxForPers(PersHandle pers)
{
	MailboxNodeHandle node;
	Str255 inbox, mName;
		
	// a non-personality has no inbox ...
	if (pers == nil) return (nil);
	
	GetRString(inbox, IMAP_INBOX_NAME);
	
	// scan only the top level of mailboxes for the inbox
	node = (*pers)->mailboxTree;
	while (node)
	{
		LockMailboxNodeHandle(node);
		PathToMailboxName((*node)->mailboxName, mName, (*node)->delimiter);
		UnlockMailboxNodeHandle(node);
		
		if (StringSame(inbox, mName)) break;
		
		node = (*node)->next;
	}
	
	return (node);
}

/***************************************************************************
 * IMAPRenameMailbox - rename an imap mailbox
 ***************************************************************************/
Boolean IMAPRenameMailbox(FSSpecPtr cacheFolderSpec, UPtr name)
{
	Boolean result = false;
	PersHandle pers = nil;
	MailboxNodeHandle node = nil;
	IMAPStreamPtr imapStream = nil;
	PersHandle oldPers = CurPers;
	OSErr err = noErr;
	char newName[MAILTMPLEN + 4];
	FSSpec cacheFileSpec, newSpec;
	Str63 newCacheName;
	TOCHandle tocH = nil;
	
	// make sure we have an old name and a new name
	if (!cacheFolderSpec || !name) return (false);
	
	// figure out who this mailbox belongs to
	SimpleMakeFSSpec(cacheFolderSpec->vRefNum, SpecDirId(cacheFolderSpec), cacheFolderSpec->name, &cacheFileSpec);
	LocateNodeBySpecInAllPersTrees(&cacheFileSpec, &node, &pers);
	
	// figure out the new name of the mailbox	
	if ((pers != nil) && (node != nil))	
	{
		// must be online to do this
		if (Offline && GoOnline()) return (false);
	
		// make sure the new name doesn't have any delimiters in it.
		if (PIndex(name, (*node)->delimiter)) IMAPError(kIMAPRenameMailbox,kIMAPMailboxNameInvalid,errIMAPMailboxNameInvalid);
		else
		{
			// first, figure out the name of the new mailbox.  It's going to have the same path as the original.
			NewIMAPMailboxName(node, name, newName);
			if (newName[0])
			{
				// the cache file name is going to be something like the path name ...
				PathToMailboxName(newName, newCacheName, (*node)->delimiter);
									
				// tell the filters we're about to rename a mailbox
				newSpec = cacheFileSpec;
				PCopy(newSpec.name, newCacheName);
				TellFiltMBRename(&cacheFileSpec,&newSpec,false,true,false);
								
				// Set up connection to the server
				CurPers = pers;
				if (imapStream = GetIMAPConnection(UndefinedTask, CAN_PROGRESS))
				{	
					// if the mailbox rename fails, but the new name is the same as the old name, continue
					// to rename the cache file.  Maybe it was a case change in the name.
					LDRef(node);
					if (((result = RenameIMAPMailbox(imapStream, (*node)->mailboxName, newName))==true) 
						|| StringSame(cacheFileSpec.name, newCacheName))
					{		
						//
						// rename the local cache folder and file
						//
						
						if (HRename(cacheFileSpec.vRefNum,cacheFileSpec.parID,cacheFileSpec.name,newCacheName)==noErr)
							HRename(cacheFolderSpec->vRefNum,cacheFolderSpec->parID,cacheFolderSpec->name,newCacheName);
						
						// update the mailboxNode with the new name.  Do this so TransferUIDValidty does the right thing.
						fs_give((void**) &((*node)->mailboxName));
						(*node)->mailboxName = cpystr(newName);
						PCopy((*node)->mailboxSpec.name, newCacheName);
						
						// rename the window if it's open
						if (tocH = FindTOC(&cacheFileSpec))
						{
							TOCSetDirty(tocH,true);
							PCopy((*tocH)->mailbox.spec.name,newCacheName);
							SetWTitle_(GetMyWindowWindowPtr((*tocH)->win),newCacheName);
						}
				
						// clean up the connection used to rename the mailbox
						CleanupConnection(&imapStream);
						
						// tell the filters about the renamed mailbox
						TellFiltMBRename(&cacheFileSpec,&newSpec,false,false,false);
						
						//
						// update the local cache
						//
						
						// if this node has children, we may have to rename or move them around.
						if ((*node)->childList) err = CreateLocalCache();
						// otherwise, just rename the mailbox
						else err = UpdateLocalCache(false);
					}
					else // rename mailbox failed, and we were told it did.
					{
						IMAPError(kIMAPRenameMailbox,kIMAPRenameMailboxErr,errIMAPRenameMailbox);
					}
					UL(node);
					
					// clean up after our connection
					if (imapStream) CleanupConnection(&imapStream);
				}
				
				CurPers = oldPers;
			}
			// else
				// the new name is invalid
		}
	}
	// else 
		// this wasn't an IMAP mailbox.
		
	return (true);
}

/***************************************************************************
 * IMAPMoveMailbox - move an IMAP mailbox
 *	If lastToMove, then the cache is refreshed.  Also, if oneMoved and
 *	the rename fails, we refresh the cache as well
 ***************************************************************************/
Boolean IMAPMoveMailbox(FSSpecPtr fromFolderSpec, FSSpecPtr toSpec, Boolean lastToMove, Boolean oneMoved, Boolean *dontWarn)
{
	Boolean result = false;
	PersHandle toPers = nil, fromPers = nil;
	MailboxNodeHandle toNode = nil, fromNode = nil;
	IMAPStreamPtr imapStream = nil;
	PersHandle oldPers = CurPers;
	OSErr err = noErr;
	char *newMailboxName = 0;
	FSSpec fromSpec, newSpec;

	// make sure we have an old name and a new name
	if (!fromFolderSpec || !toSpec) return (false);
	
	// if the dirIDs of the two specs are the same, then we're not going to actually have to transfer anythin.
	if (fromFolderSpec->parID == toSpec->parID) return (true);
	
	// we'll be dealing with the file inside the fromFolder
	SimpleMakeFSSpec(fromFolderSpec->vRefNum, fromFolderSpec->parID, fromFolderSpec->name, &fromSpec);
	fromSpec.parID = SpecDirId(&fromSpec);
			
	// figure out who the mailboxes belong to
	LocateNodeBySpecInAllPersTrees(toSpec, &toNode, &toPers);
	if (toNode && toPers)
	{
		LocateNodeBySpecInAllPersTrees(&fromSpec, &fromNode, &fromPers);
		if (fromNode && fromPers)
		{
			// Currently only support moving mailboxes on the same server
			if ((fromPers == toPers))
			{
				// allow moves to same location.  Do nothing.
				if (toNode == fromNode) return (true);

				// must be online to do this
				if (Offline && GoOnline()) return (false);
				
				// must be able to modify the mailbox tree, too.
				if (!CanModifyMailboxTrees()) return (false);
		
				CurPers = toPers;
				// the new mailbox name will be the destination plus the source name
				if (BuildIMAPMailboxName(toNode, &fromSpec, ((*fromNode)->childList!=nil), &newMailboxName)==noErr)
				{
					// Set up connection to the server
					if (imapStream = GetIMAPConnection(UndefinedTask, CAN_PROGRESS))
					{	
						LDRef(fromNode);
						if ((result = RenameIMAPMailbox(imapStream, (*fromNode)->mailboxName, newMailboxName))==true)
						{		
							// tell the filters we're about to move a mailbox
							TellFiltMBRename(&fromSpec,&fromSpec,false,true,false);

							// try to move the cache folder so we won't have to redownload everything
							if (HMove(fromFolderSpec->vRefNum,fromFolderSpec->parID,fromFolderSpec->name,toSpec->parID,nil)==noErr)
							{			
								// update the mailboxNode with the new path info.  Do this so TransferUIDValidty does the right thing.
								fs_give((void **)&((*fromNode)->mailboxName));
								(*fromNode)->mailboxName = cpystr(newMailboxName);
							}
						
							// newSpec will point to the newly moved cache file ...
							newSpec = *toSpec;
							PCopy(newSpec.name, fromSpec.name);
							newSpec.parID = SpecDirId(&newSpec);
							
							// and tell the filters about the renamed mailbox
							TellFiltMBRename(&fromSpec,&newSpec,false,false,*dontWarn);
							
							// and don't warn again.
							*dontWarn = true;
						}
						else
						{
							IMAPError(kIMAPMoveMailbox,kIMAPMoveMailboxErr,errIMAPMoveMailbox);
						}
						UL(fromNode);
						
						// clean up
						CleanupConnection(&imapStream);
												
						// rebuild the mailbox tree for this personality if we need to.
						if (lastToMove || (!result && oneMoved)) err = CreateLocalCache();
							
						ZapPtr(newMailboxName);
						if (imapStream) CleanupConnection(&imapStream);
					}
					else
						WarnUser(MEM_ERR,err);
					
				}
				else	// failed to get the new name.  Memory Error!
					WarnUser(MEM_ERR,err);
				
				CurPers = oldPers;
			}
		}
	}
		
	return (result);
}

/***************************************************************************
 * NewIMAPMailboxName - given a mailbox path name, and a new name, build
 *	a new path name.
 ***************************************************************************/
char *NewIMAPMailboxName(MailboxNodeHandle node, UPtr name, char *newName)
{
	char *scan;
	
	if (!node || !name || !*name || !newName) return (nil);
	
	// the new name will contain all but the last directory in the old name
	strcpy(newName,(*node)->mailboxName);
	// chop off trailing delimiter if there is one
	if (newName[strlen(newName)-1] == (*node)->delimiter) newName[strlen(newName)-1] = nil;
	// chop off everything up until the last delimiter
	while (scan = &newName[strlen(newName)-1])
	{
		if ((*scan != (*node)->delimiter) && scan >= newName) *scan = nil;
		else break;
	}
	// now add the new mailbox name
	strncat(newName,name+1,name[0]);
	
	return (newName);
}

/***************************************************************************
 * CanModifyMailboxTrees - return whether we are allowed to touch the tree
 ***************************************************************************/
Boolean CanModifyMailboxTrees(void)
{
	threadDataHandle index;
		
	if (GetNumBackgroundThreads()!=0)
	{
		// there are threads going.  If there is anything other than a
		// filtering thread going, we shouldn't touch the tree.
		for (index=gThreadData;index;index=(*index)->next)
		{
			if ((*index)->imapInfo.command != IMAPFilterTask)
				return (false);
		}	
	}
	return (true);
}

/***************************************************************************
 * GetSpecialMailbox - return a handle to the special mailbox node.
 ***************************************************************************/
MailboxNodeHandle GetSpecialMailbox(PersHandle pers, Boolean createIfNeeded, Boolean silent, long mboxAtt)
{
	MailboxNodeHandle specialMBox = nil;
	PersHandle oldPers = CurPers;
	SignedByte state;
	
	// locate the mailbox flagged as the trash mailbox.
	CurPers = pers;
	state = HGetState((Handle)CurPers);
	LDRef(CurPers);
	if ((specialMBox = LocateSpecialMailbox((*CurPers)->mailboxTree, mboxAtt))==nil)
	{
		// node was found.  Try and create it if we were asked to
		if (!createIfNeeded || ((specialMBox=CreateSpecialMailbox(true, mboxAtt))==nil))
		{
			// failed to locate the special mailbox.  Operation must not continue.
			if (!silent) 
			{
				// Display proper error message depending on what we're doing
				if (SpecialMailboxIsTrash(mboxAtt))
					IMAPError(kIMAPTrashLocate, kIMAPTrashLocateErr, errIMAPNoTrash);
				else if (SpecialMailboxIsJunk(mboxAtt) && !JunkPrefCantCreateJunk())
					IMAPError(kIMAPJunkLocate, kIMAPJunkLocateErr, errIMAPNoTrash);
			}
		}
		else 
		{
			MBTickle(nil, nil);
		}
	}
	HSetState((Handle)CurPers, state);
	CurPers = oldPers;

	return (specialMBox);
}

/***************************************************************************
 * ResetSpecialMailbox - forget about a special mailbox for a personality
 ***************************************************************************/
void ResetSpecialMailbox(PersHandle pers, long mboxAtt)
{
	MailboxNodeHandle specialMBox = nil;
	PersHandle oldPers = CurPers;
	SignedByte state;
	
	// locate the mailbox flagged as the trash mailbox.
	CurPers = pers;
	state = HGetState((Handle)CurPers);
	LDRef(CurPers);
	
	while ((specialMBox = LocateSpecialMailbox((*CurPers)->mailboxTree, mboxAtt))!=nil)
		(*specialMBox)->attributes &= ~mboxAtt;

	HSetState((Handle)CurPers, state);
	CurPers = oldPers;
	
	MBTickle(nil,nil);
}

/***************************************************************************
 * LocateSpecialMailbox - given a tree, find the first (and only) occurence
 *	of a special mailbox for the current personality.
 ***************************************************************************/
MailboxNodeHandle LocateSpecialMailbox(MailboxNodeHandle tree, long mboxAtt)
{
	MailboxNodeHandle scan = tree;
	MailboxNodeHandle node = nil;
		
	while (scan)
	{
		// is this the node we're looking for?
		if (IsIMAPSpecialMailbox(scan, mboxAtt)) 
		{
			node = scan;
			break;
		}
		
		// is the node we're looking for one of the children of this node?
		LockMailboxNodeHandle(scan);
		if ((*scan)->childList) node = LocateSpecialMailbox(((*scan)->childList), mboxAtt);
		UnlockMailboxNodeHandle(scan);
		
		if (node) break;
		
		// otherwise, check the next node.
		scan = (*scan)->next;
	}
	return (node);
}

/***************************************************************************
 * CreateSpecialMailbox - create a special mailbox for the current pers
 ***************************************************************************/
MailboxNodeHandle CreateSpecialMailbox(Boolean tryCreate, long mboxAtt)
{
	MailboxNodeHandle specialMbox = nil;
	MailboxNodeHandle inbox = nil;
	FSSpec specialSpec;
	Str255 specialMailboxName, scratch;
	OSErr err = noErr;
 	Boolean created = false;
	SignedByte state;
	long reuseWarning, chooseMessage, selectMessage;
	
	// Don't do this if the user knows better.  Yeah right.
	if (JunkPrefCantCreateJunk()) return (nil);
	 
 	// can't do this from a background thread, or if there's one running.
	if (!CanModifyMailboxTrees()) return (nil);
	
	// set up mailbox name and messages depending on what kind of
	// special mailbox we're going to create.
	if (SpecialMailboxIsTrash(mboxAtt))
	{
		reuseWarning = IMAP_TRASH_REUSE;
		chooseMessage = CHOOSE_IMAP_TRASH_MAILBOX;
		selectMessage = IMAP_TRASH_SELECT;
		GetRString(specialMailboxName, TRASH);	
	}	
	else if (SpecialMailboxIsJunk(mboxAtt))
	{	
		reuseWarning = IMAP_JUNK_SELECT;
		chooseMessage = CHOOSE_IMAP_JUNK_MAILBOX;
		selectMessage = IMAP_JUNK_SELECT;
		GetRString(specialMailboxName, JUNK);
	}
	specialMailboxName[specialMailboxName[0]+1] = NULL;
	
	if (tryCreate)
	{ 	
		// locate this personality's INBOX
		inbox = LocateInboxForPers(CurPers);
		if (inbox)
		{
			// try to create the special mailbox at the same level as the inbox
			PersNameToCacheName(CurPers, scratch);
			err = FSMakeFSSpec(IMAPMailRoot.vRef,IMAPMailRoot.dirId,scratch,&specialSpec);
			if (err == noErr)
			{
				specialSpec.parID = SpecDirId(&specialSpec);
				PCopy(specialSpec.name, specialMailboxName);
				IMAPAddMailbox(&specialSpec, false, &created, true);
				if (created)
				{
					// locate the special mailbox we just created.
					state = HGetState((Handle)CurPers);
					LDRef(CurPers);
					specialMbox = LocateNodeByMailboxName((*CurPers)->mailboxTree, specialMailboxName+1);
					HSetState((Handle)CurPers, state);
				}
			}
		}
	}
	
	// Failed to create the special mailbox.  See if it already exists in the root level
	if (!specialMbox)
	{
		// find the special mailbox
		state = HGetState((Handle)CurPers);
		LDRef(CurPers);
		specialMbox = LocateNodeByMailboxName((*CurPers)->mailboxTree, specialMailboxName+1);
		HSetState((Handle)CurPers, state);
		
		// did we find a mailbox with the same name?
		if (specialMbox)
		{
			// warn the user about using it
			if (ComposeStdAlert(Note,reuseWarning,specialMailboxName,(*CurPers)->name)==2) 			
				specialMbox = nil;
		}
	}
	
	// Still no special mailbox.  Have the user pick one from the menus, unless we've failed before.
	if (!specialMbox)
	{
		if (ChooseSpecialMailbox(CurPers, chooseMessage, &specialSpec))
		{
			state = HGetState((Handle)CurPers);
			LDRef(CurPers);
			specialMbox = LocateNodeBySpec((*CurPers)->mailboxTree, &specialSpec);
			HSetState((Handle)CurPers, state);
					
			// Make sure the user really wants to use this mailbox as the special mailbox.
			if (specialMbox && (ComposeStdAlert(Note,selectMessage,specialSpec.name,(*CurPers)->name)==2)) specialMbox = nil;	
		}
	}
	
	// a special mailbox was either created or selected. Now go tell the mailbox it's been annointed
	if (specialMbox)
	{
		(*specialMbox)->attributes |= mboxAtt;
		LockMailboxNodeHandle(specialMbox);
		err = WriteIMAPMailboxInfo(&((*specialMbox)->mailboxSpec),specialMbox);
		UnlockMailboxNodeHandle(specialMbox);
	}
					
	return (specialMbox);
}

/************************************************************************
 * LocateIMAPJunkToc - given an IMAP mailbox, return the appropriate
 *	junk TOC.	
 ************************************************************************/
TOCHandle LocateIMAPJunkToc(TOCHandle tocH, Boolean createIfNeeded, Boolean silent)
{
	PersHandle pers;
	MailboxNodeHandle mbox;
	
	if (tocH && (*tocH)->imapTOC)
	{
		pers = TOCToPers(tocH);
		if (pers)
		{
			mbox = GetIMAPJunkMailbox(pers, createIfNeeded, silent);
			if (mbox)
			{				
				return (TOCBySpec(&(*mbox)->mailboxSpec));	
			}
		}
	}
	return (NULL);
}

/************************************************************************
 *	IsIMAPSpecialMailbox - return true if this is a special IMAP mailbox
 ************************************************************************/
Boolean IsIMAPSpecialMailbox(MailboxNodeHandle node, long att)
{
	return ((node != NULL) && (((*node)->attributes&att)!=0));
}

/************************************************************************
 *	CanHaveChildren - return true if this node can have children
 ************************************************************************/
Boolean CanHaveChildren(MailboxNodeHandle node)
{
	return (((*node)->attributes&LATT_NOINFERIORS)==0);
}

/************************************************************************
 * IMAPEmptyTrash - empty the trash mailboxes that need it.  Return
 *	true if no other emptying needs to be done.	
 ************************************************************************/
Boolean IMAPEmptyTrash(Boolean localOnly, Boolean currentOnly, Boolean all)
{
	Boolean emptyLocal = false;
	
	if (all)
	{
		//
		// empty all trash mailboxes
		//
	
		EmptyImapTrashes(kEmptyAllTrashes);
		emptyLocal = true;	// and have caller empty the local trash, too.
	}
	else if (localOnly)
	{
		//
		// empty local trash mailbox only
		//
		
		emptyLocal = true;	
	}
	else if (currentOnly)
	{
		//
		// empty current trash mailbox only
		//
		
		WindowPtr	winWP = MyFrontWindow();
		MyWindowPtr win = GetWindowMyWindowPtr(winWP);
		short kind = winWP ? GetWindowKind(winWP) : 0;
		TOCHandle tocH = nil;
		
		// look at the frontmost window
		if (win && kind == MBOX_WIN) tocH = (TOCHandle)GetMyWindowPrivateData(win);
		
		if (tocH)
		{
			if ((*tocH)->imapTOC)
			{
				// frontmost window is an IMAP mailbox.  Empty the trash for the personality it belongs to.
				PushPers(TOCToPers(tocH));
				if (CurPers) IMAPEmptyPersTrash();
				PopPers();
				
				emptyLocal = false;	// empty the local trash.
			}
			else
			{
				// frontmost window is a POP mailbox.  Empty the local trash.
				emptyLocal = true;
			}
		}
		else
		{
			// no front window.  Empty the local trash.
			emptyLocal = true;
		}
	}
	else
	{
		//
		// Local trash, and any active IMAP trashes
		//
		
		EmptyImapTrashes(kEmptyActiveTrashes);
		emptyLocal = true;	// and have caller empty the local trash, too.
	}
	
	return (emptyLocal);
}

/************************************************************************
 *	IMAPCountTrashMessages - return the number of messages in the local
 *	 IMAP trash mailboxes.
 ************************************************************************/
short IMAPCountTrashMessages(Boolean localOnly, Boolean currentOnly, Boolean all)
{
	short totalFound = 0;
	MailboxNodeHandle trash = nil;
	TOCHandle tocH = nil;
	PersHandle pers;
	FSSpec mailboxSpec;
	
	if (!localOnly)
	{
		// We are checking remote IMAP mailboxes for messages, at least.  We must be online
		if (Offline && GoOnline()) return(0);
		
		if (currentOnly)
		{
			//
			// Only Current trash mailbox
			//
			
			WindowPtr	winWP = MyFrontWindow ();
			MyWindowPtr win = GetWindowMyWindowPtr(winWP);
			short kind = winWP ? GetWindowKind(winWP) : 0;
			TOCHandle tocH = nil;
			TOCHandle trashToc = nil;
			
			// look at the frontmost window
			if (win && kind == MBOX_WIN) tocH = (TOCHandle)GetMyWindowPrivateData(win);
			
			if (tocH && ((*tocH)->imapTOC))
			{
				// figure out which IMAP personality this mailbox belongs to
				pers = TOCToPers(tocH);
				if (pers)
				{
					// find the trash mailbox of the personality
					trash = GetIMAPTrashMailbox(pers, false, true);
					if (trash)
					{
						// see how many messages are in the trash
						LockMailboxNodeHandle(trash);
						trashToc = TOCBySpec(&((*trash)->mailboxSpec));
						UnlockMailboxNodeHandle(trash);
						if (trashToc)
						{
							mailboxSpec = GetMailboxSpec(trashToc, -1);
							totalFound += IMAPMailboxMessageCount(&mailboxSpec, true);
						}
					}
				}
			}
		}
		else
		{
			//
			// All Trash Mailboxes or Active Trash Mailboxes
			//
			for (pers = PersList; pers; pers = (*pers)->next)
			{
				// is this an IMAP personality?
				if (IsIMAPPers(pers))
				{
					// consider only active personalities, unless we all is true
					if (all || IMAPPersActive(pers))
					{
						trash = GetIMAPTrashMailbox(pers, false, true);
						if (trash)
						{
							LockMailboxNodeHandle(trash);
							tocH = TOCBySpec(&((*trash)->mailboxSpec));
							UnlockMailboxNodeHandle(trash);
							if (tocH)
							{
								mailboxSpec = GetMailboxSpec(tocH, -1);
								totalFound += IMAPMailboxMessageCount(&mailboxSpec, true);
							}
						}
					}
				}
			}
		}
	}
	return (totalFound);
}

/************************************************************************
 * ChooseSpecialMailbox - let the user pick a mailbox from the menus
 ************************************************************************/
Boolean ChooseSpecialMailbox(PersHandle pers, short msg, FSSpecPtr specialSpec)
{	
	short mId;
	WindowPtr theWindow;
	EventRecord event;
	MyWindowPtr	dgPtrWin;
	DialogPtr dgPtr;
	Boolean rslt = false;
	Str255 msgStr;
	MenuHandle mh = GetMHandle(MAILBOX_MENU);
	short count;
	Str255 itemText;
	MenuHandle submh;
	short submId;
	short subCount;
	Str255 inboxStr;
	
	PushGWorld();
	
	//
	// put up message and disable all but the Mailbox menu
	//
	 
	// disable all the menus but the Mailbox menu
	for (mId=APPLE_MENU;mId<MENU_LIMIT;mId++) DisableItem(GetMHandle(mId),0);
	DisableItem(GetMHandle(WINDOW_MENU),0);
	EnableItem(mh,0);
	DrawMenuBar();
	
	// disable all the items in the Mailbox menu except for the personality cache
	for (count = 1; count <= CountMenuItems(mh); count++)
	{
		MyGetItem(mh,count,itemText);
		EnableIf(mh, count, StringSame((*pers)->name,itemText));
		if (StringSame((*pers)->name,itemText))
		{
			// disable the Inbox item within the personality cache, unless it has children
			GetRString(inboxStr, IMAP_INBOX_NAME);
			if (submId = SubmenuId(mh,count))
			{
				if (submh=GetMHandle(submId))
				{
					for (subCount = 1; subCount <= CountMenuItems(submh); subCount++)
					{
						MyGetItem(submh,subCount,itemText);
						if (StringSame(inboxStr,itemText))
						{
							if (SubmenuId(submh, subCount) == 0)
							{
								EnableIf(submh, subCount, !StringSame(inboxStr,itemText));
							}
							break;
						}
					}
				}
			}
		}
	}	
	
	ParamText(GetRString(msgStr,msg),"","","");
	dgPtrWin = GetNewMyDialog (XFER_MENU_DLOG,nil,nil,InFront);
	dgPtr = GetMyWindowDialogPtr (dgPtrWin);
//	dgPtr = GetNewDialog(XFER_MENU_DLOG,nil,InFront);
	AutoSizeDialog (dgPtr);
	DrawDialog(dgPtr);
	SetMyCursor(arrowCursor); SFWTC = True;
	
	// Forget about any clicking that was done to get here.  
	// We may be doing this immediately after a drag, for example.
	FlushEvents(mDownMask|mUpMask|keyDownMask,0);
			
	//
	//  now, let the user choose something:
	//
	
	for (;;)
	{
		if (WNE(mDownMask|keyDownMask|updateMask,&event,REAL_BIG))
		{
			if (event.what==updateEvt) MiniMainLoop(&event);
			//	Check for mouseDown and keyDown events. WNE in OS X
			//	is currently return more than what's in the event mask. 
			else if (event.what==mouseDown || event.what==keyDown) break;
		}
	}
	if (event.what==mouseDown)
	{
		if (inMenuBar == FindWindow_(event.where,&theWindow))
		{
			long mSelect = MenuSelect(event.where);
			short mnu, itm;
			
			mnu = (mSelect>>16)&0xffff;
			itm = mSelect & 0xffff;
			if (mnu==MAILBOX_MENU || IsMailboxSubmenu(mnu))
			{
				rslt = itm && GetTransferParams(mnu,itm,specialSpec,nil);
			}
			HiliteMenu(0);
		}
	}
	DisposDialog_(dgPtr);
	// re-enable the menus
	for (count = 1; count <= CountMenuItems(mh); count++)
	{
		MyGetItem(mh,count,itemText);
		EnableIf(mh, count, true);
		if (StringSame((*pers)->name,itemText))
		{
			// re-enable the Inbox item within the personality cache
			if (submId = SubmenuId(mh,count))
			{
				if (submh=GetMHandle(submId))
				{
					for (subCount = 1; subCount <= CountMenuItems(submh); subCount++)
					{
						EnableIf(submh, subCount, true);
					}
				}
			}
		}
	}
	EnableMenus(FrontWindow_(),False);
	PopGWorld();
	return(rslt);
}

/************************************************************************
 * FancyTrashForThisPers - return true is FTM is on for the personality
 *	that owns the mailbox specified by the TOC
 ************************************************************************/
Boolean FancyTrashForThisPers(TOCHandle tocH)
{
	Boolean result = false;
	MailboxNodeHandle node = nil;
	PersHandle pers = nil;
	PersHandle oldPers = CurPers;
	
	// must have a tocH
	if (tocH)
	{	
		// must be an IMAP toc
		if (!(*tocH)->imapTOC) return (false);
		{	
			// who does this toc belong to?
			node = TOCToMbox(tocH);
			pers = TOCToPers(tocH);
			if (node && pers)
			{
				// see if FTM is on for this personality
				CurPers = pers;
				result = !PrefIsSet(PREF_IMAP_NO_FANCY_TRASH);
				CurPers = oldPers;
			}
		}
	}
	return (result);
}

/************************************************************************
 * MailboxTreeGood - return true if the mailbox list for pers is good
 ************************************************************************/
Boolean MailboxTreeGood(PersHandle pers)
{	
	return ((pers) && ((*pers)->mailboxTree) && (((*((*pers)->mailboxTree))->next) || ((*((*pers)->mailboxTree))->childList)));
}

/************************************************************************
 * GetIMAPAttachFolder - make a spec for a file in the current
 *	personality's imap attachments directory.
 *
 *	Note, we use FSPExchangeFiles to turn stubs into full attachments.
 *	So the stub has to start its life on the same volume.
 ************************************************************************/
OSErr GetIMAPAttachFolder(FSSpecPtr attachSpec)
{
	OSErr err = noErr;
	FSSpec spec, attachFolderSpec, imapStubSpec;
	Str255 cacheName, name;
	
	PersNameToCacheName(CurPers, cacheName);
		 			
	// if the stub folder and attachment folder is on the same drive, use the IMAP Attachments folder in
	// the IMAP cache directory
	GetAttFolderSpec(&attachFolderSpec);
	if (attachFolderSpec.vRefNum == IMAPMailRoot.vRef)
	{			 			
		if ((err=FSMakeFSSpec(IMAPMailRoot.vRef,IMAPMailRoot.dirId,cacheName,&spec))==noErr)
		{	
			if ((err=FSMakeFSSpec(spec.vRefNum,SpecDirId(&spec),GetRString(name,IMAP_ATTACH_FOLDER),&spec))==noErr)
			{
				attachSpec->vRefNum = spec.vRefNum;
				attachSpec->parID = SpecDirId(&spec);
			}
		}
	}
	else
	{
		// otherwise, make sure there's a stub directory in the attachment folder
		if ((err=EnsureIMAPCacheFolders(&attachFolderSpec, &imapStubSpec))==noErr)
		{
			// and use the stub folder on the other volume
		 	attachSpec->vRefNum = imapStubSpec.vRefNum;
		 	attachSpec->parID = SpecDirId(&imapStubSpec);
		}
	}
	return (err);
}

/************************************************************************
 * EnsureIMAPCacheFolders - build a hierarchy of attachment folders for
 *	IMAP stubs inside the attach directory.  Stub is returned pointing
 *	to the IMAP Attachment Stub directory of the current personality.
 ************************************************************************/
OSErr EnsureIMAPCacheFolders(FSSpecPtr attach, FSSpecPtr imapAttach)
{
	OSErr err = noErr;
	Str255 name, cacheName;
	PersHandle oldPers = CurPers;
	FSSpec spec, persSpec, stubSpec;
	long junk;
	
	//
	// create a folder inside the attach folder to enclose the personality caches
	//
	
	err = FSMakeFSSpec(attach->vRefNum,attach->parID,GetRString(name,IMAP_SAFE_ATTACH_FOLDER),&spec);
	if (err == fnfErr)
	{
		// create a folder for all the personality caches
		err = FSpDirCreate(&spec,smSystemScript,&junk);
	}
	
	if (err == noErr)
	{
		//
		// Loop through the personalities, creating a folder with the same name as the personality
		//
		
		for (CurPers=PersList;CurPers && (err==noErr);CurPers=(*CurPers)->next)
		{
			// if this is an IMAP personality ...
			if (IsIMAPPers(CurPers))
			{
				// see if the personality already has a cache
			 	PersNameToCacheName(CurPers, cacheName);
			 	
			 	err = FSMakeFSSpec(spec.vRefNum,SpecDirId(&spec),cacheName,&persSpec);	
			 	if (err == fnfErr)  	// it doesn't.
			 	{
			 		// create a cache folder for this personality
			 		err = FSpDirCreate(&persSpec,smSystemScript,&junk);
			 	}
			 	
			 	if (err == noErr)
			 	{
			 		// create an IMAP Attachments folder inside this folder
			 		err = FSMakeFSSpec(persSpec.vRefNum,SpecDirId(&persSpec),GetRString(name,IMAP_ATTACH_FOLDER),&stubSpec);	
				 	if (err==fnfErr) err = FSpDirCreate(&stubSpec,smSystemScript,&junk);
				 	
				 	// remember this directory
				 	if (CurPers == oldPers) *imapAttach = stubSpec;
			 	}
			}
		}
		CurPers = oldPers;
	}
		
	return (err);
}

/************************************************************************
 * TOCToMbox - given a TOC, return a handle to it's mailboxnode
 ************************************************************************/
MailboxNodeHandle TOCToMbox(TOCHandle tocH)
{
	return ((*tocH)->imapMBH);
}

/************************************************************************
 * TOCToPers - given a TOC, return the owning personality
 ************************************************************************/
PersHandle TOCToPers(TOCHandle tocH)
{
	return (tocH ? MailboxNodeToPers((*tocH)->imapMBH) : NULL);
}

/************************************************************************
 * MailboxNodeToPers - given a MailboxNodeHandle, return the owning 
 *	personality
 ************************************************************************/
PersHandle MailboxNodeToPers(MailboxNodeHandle mbox)
{
	return (mbox ? FindPersById((*mbox)->persId) : NULL);
}

/************************************************************************
 * SalvageIMAPTOC - additional step to salvage an IMAP toc.
 *
 * Since IMAP messages reside on the server, this is not as big of a deal
 *	as a local mailbox.  To rebuild an IMAP toc, we do the following:
 *
 *	- SlavageTOC() to save all downloaded messages.  This is currently
 *		done by the caller.
 *
 *	- If there were some salvagable messages, the TOC is probably more or 
 *		less in order.  Save all the minimal headers.
 *
 *	- If we saved any minimal headers, go through the new TOC and check 
 *		for duplicate minimal headers.
 *
 *	Also, be sure to update the subject, from, and status when fetching
 *	 an IMAP message from the server, just in case.
 ************************************************************************/
void SalvageIMAPTOC(TOCHandle oldTocH, TOCHandle newTocH, short *newCount)
{
	short sumNum;
	short minHeaderCount;
	short i, j;
	
	// Only do this on an IMAP toc!
	if ((*oldTocH)->imapTOC && (*newTocH)->imapTOC)
	{
		//
		// remove all messages with bogus UIDs
		//
		
		for (sumNum = (*newTocH)->count - 1; sumNum >= 0; sumNum--)
		{
			if (((*newTocH)->sums[sumNum].msgIdHash == (*newTocH)->sums[sumNum].uidHash)	// bogus UID
				|| !ValidHash((*newTocH)->sums[sumNum].uidHash))						// no UID
			{
				DeleteIMAPSum(newTocH,sumNum);
				if (newCount && *newCount) *newCount = *newCount - 1;
			}
		}
		
		//
		//	Save the minimal headers if any messages were slavagable
		//
		
		if (*newCount > 0)
		{
			minHeaderCount = 0;
			for (i = 0; i < (*oldTocH)->count; i++)
			{
				if (!IMAPMessageDownloaded(oldTocH, i))
				{
					SaveMessageSum(&(*oldTocH)->sums[i],&newTocH);
					minHeaderCount++;
				}
			}
		
			//
			// Check for headers with duplicate ids.
			//
			
			if (minHeaderCount > 0)
			{
				// we copied some minimal headers.  Go through each one and make sure it has a unique UID.
				for (i = 0; i < (*newTocH)->count; i++)
				{
					if (!IMAPMessageDownloaded(newTocH, i))
					{
						for (j = 0; j < (*newTocH)->count; j++)
						{
							if ((i!=j) && !IMAPMessageDownloaded(newTocH, j))
							{
								if ((*newTocH)->sums[i].uidHash == (*newTocH)->sums[j].uidHash)
								{
									// duplicate found.  Remove the duplicate
									DeleteIMAPSum(newTocH, j);
									minHeaderCount--;
									j--;	// and make sure we don't skip the next one.
								}
							}
						}
					}
				}
			}
			
			// add the number of salvaged minimal headers to the new count
			*newCount += minHeaderCount;
		}
	}
}

/************************************************************************
 * LockMailboxNodeHandle - makes sure the mailbox node stays locked in
 *	memory
 ************************************************************************/
void LockMailboxNodeHandle(MailboxNodeHandle node)
{
	LDRef(node);
	(*node)->lockCount++;
}

/************************************************************************
 * UnlockMailboxNodeHandle - decrements lock count, unlocks mailbox node
 *	in memory once count reaches 0.
 ************************************************************************/
void UnlockMailboxNodeHandle(MailboxNodeHandle node)
{
	(*node)->lockCount--;
	if ((*node)->lockCount < 1)
	{
		(*node)->lockCount = 0;	// handicap for my mental retardation
		UL(node);
	}
}

/************************************************************************
 * ClosePersMailboxes - close all mailboxes belonging to a personality.
 *	This is necessary when an IMAP cache is removed.
 ************************************************************************/
void ClosePersMailboxes(PersHandle pers)
{
	TOCHandle tocH, nextTocH;
	
	for (tocH=TOCList; tocH; tocH = nextTocH)
	{
		nextTocH = (*tocH)->next;
		
		// should we close this toc?
		if (TOCToPers(tocH) == pers)
		{
			// close the IMAP mailbox, skip any final processing
			CloseIMAPMailboxImmediately(tocH, false);
		}
	}
}

/************************************************************************
 * CloseChildMailboxes - close all child mailboxes.  This is necessary 
 *	when an IMAP mailbox is removed.
 ************************************************************************/
void IMAPCloseChildMailboxes(FSSpecPtr spec)
{
	MailboxNodeHandle node;
	PersHandle pers;
	TOCHandle tocH, nextTocH;
	FSSpec tocSpec;
		
	// does this mailbox have children?
	LocateNodeBySpecInAllPersTrees(spec, &node, &pers);
	if (node && (*node)->childList)
	{
		// iterate over all open windows ...
		for (tocH=TOCList; tocH; tocH = nextTocH)
		{
			nextTocH = (*tocH)->next;
			
			// should we close this toc?
			tocSpec = (*tocH)->mailbox.spec;
			if (LocateNodeBySpec((*node)->childList, &tocSpec))
			{
				// close the IMAP mailbox, skip any final processing
				CloseIMAPMailboxImmediately(tocH, false);
			}
		}	
	}
}

/************************************************************************
 * CloseIMAPMailboxImmediately - close an IMAP mailbox, skip final
 *  processing.  Close the hidden mailbox if requested
 ************************************************************************/
void CloseIMAPMailboxImmediately(TOCHandle tocH, Boolean bHiddenToo)
{
	MailboxNodeHandle mbox;
	short sumNum;
	
	if (tocH)
	{
		// skip final processing
		mbox = TOCToMbox(tocH);
		if (mbox)
		{
			// don't try to execute its queued commands
			SetIMAPMailboxNeeds(mbox, kNeedsExecCmd, false);
			
			// don't expunge it on quit
			SetIMAPMailboxNeeds(mbox, kNeedsAutoExp, false);
			
			// handle hidden mailbox as well.
			if (bHiddenToo)
				CloseIMAPMailboxImmediately(GetHiddenCacheMailbox(mbox, true, false), false);
		}
		
		// close all message windows
		TOCSetDirty(tocH,false);
		for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
			if ((*tocH)->sums[sumNum].messH)
				CloseMyWindow(GetMyWindowWindowPtr((*(*tocH)->sums[sumNum].messH)->win));
				
		// close the mailbox window
		if ((*tocH)->win) CloseMyWindow(GetMyWindowWindowPtr((*tocH)->win));
	}
}

			
/**********************************************************************
 * IMAPMailboxHasUnread - delude ourselves into beliving this mailbox
 *	has something important in it.
 **********************************************************************/
Boolean IMAPMailboxHasUnread(TOCHandle tocH, Boolean itDoesNow)
{
	OSErr err = noErr;
	CInfoPBRec mailboxFileInfo;
	FSSpec boxSpec = (*tocH)->mailbox.spec;
	short myMenu;
	short myItem;
	Style oldStyle;
	Boolean alreadyHadUnread = false;
	
	myItem = 0;
	Spec2Menu(&boxSpec,False,&myMenu,&myItem);

	// is the mailbox in the menus?
	if (myItem>0)
	{
		// is the mailbox already marked as having unread messages?
		GetItemStyle(GetMHandle(myMenu),myItem,&oldStyle);
		alreadyHadUnread = oldStyle==UnreadStyle;
		
		// mark it as having unread messages if we ought to
		if (!alreadyHadUnread && itDoesNow)
		{
			//	Find the mailbox cache file on disk ...
			Zero(mailboxFileInfo);
			err = HGetCatInfo(boxSpec.vRefNum,boxSpec.parID,boxSpec.name,&mailboxFileInfo);
			if (err == noErr)
			{		
				// Set the label of this mailbox so Eudora sees it as having unread mail.
				mailboxFileInfo.hFileInfo.ioFlFndrInfo.fdFlags |= 0xe;
				// Tweak the mod date, too.
				mailboxFileInfo.hFileInfo.ioFlMdDat = LocalDateTime();	
					
				err = HSetCatInfo(boxSpec.vRefNum,boxSpec.parID,boxSpec.name,&mailboxFileInfo);
				if (err == noErr) 
				{	
					// whack the menus ...
					FixSpecUnread(&boxSpec,1);
					FixMenuUnread(GetMHandle(myMenu),myItem,1);
				}
			}
		}
	}
	
	return (alreadyHadUnread);
}

/**********************************************************************
 * IMAPDontFccMailbox - return true if this mailbox shouldn't be auto-
 *	fcc'ed to.
 **********************************************************************/
Boolean IMAPDontAutoFccMailbox(TOCHandle tocH)
{
	Boolean result = false;
	MailboxNodeHandle mbox;
	PersHandle pers;
	
	// must have a toc to do Fcc at all
	if (!tocH) return (true);
	
	// if this isn't an IMAP toc, assume we can Fcc to it
	if (!(*tocH)->imapTOC) return (false);
	
	// find which mailbox this TOC points to
	mbox = TOCToMbox(tocH);
	pers = TOCToPers(tocH);
		
	// is this mailbox one that shouldn't be auto-fcc'ed to?	
	if (mbox && pers)
	{
		// Don't auto Fcc to this mailbox if this is an IMAP Inbox
		result = (mbox == LocateInboxForPers(pers));
		
		// Don't auto Fcc to this mailbox if this is an IMAP trash mailbox
		if (!result) result = IsIMAPTrashMailbox(mbox);
	}
		
	return (result);
}

/**********************************************************************
 * AttachOpenTocsToIMAPMailboxTrees - tell all open Tocs about a change
 *	in the mailbox trees
 **********************************************************************/
void AttachOpenTocsToIMAPMailboxTrees(void)
{	
	TOCHandle tocH;
	PersHandle pers;
	MailboxNodeHandle mbox;
	FSSpec mboxSpec;
	
	for (tocH=TOCList;tocH;tocH=(*tocH)->next)
	{
		if ((*tocH)->imapTOC)
		{
			mboxSpec = ((*tocH)->mailbox.spec);
			LocateNodeBySpecInAllPersTrees(&mboxSpec, &mbox, &pers);
			(*tocH)->imapMBH = mbox;
		}
	}
}

/**********************************************************************
 * SetIMAPMailboxNeeds - Set the needs member of an mailbox
 **********************************************************************/
void SetIMAPMailboxNeeds(MailboxNodeHandle node, MailboxNeedsEnum flag, Boolean on)
{
	if (node)
	{
		if (on)
			(*node)->mailboxneeds |= flag;
		else
			(*node)->mailboxneeds &= ~flag;
	}
}

/************************************************************************
 * IMAPCountMailboxes - count the number of mailboxes with a certain
 *	flag set
 ************************************************************************/
long IMAPCountMailboxes(MailboxNodeHandle tree, MailboxNeedsEnum needs)
{
	long found = 0;
	
	while (tree)
	{
		if (DoesIMAPMailboxNeed(tree, needs))
			found++;
		if ((*tree)->childList) 
			found += IMAPCountMailboxes((*tree)->childList, needs);
		tree = (*tree)->next;
	}
	return (found);
}
	
/**********************************************************************
 * UIDToSumNum - return the sumnum of the message with a given UID.
 **********************************************************************/
short UIDToSumNum(unsigned long uid, TOCHandle tocH)
{
	short sumNum;
	
	for (sumNum=0;sumNum<(*tocH)->count;sumNum++)
		if ((*tocH)->sums[sumNum].uidHash == uid)
			return (sumNum);
			
	return (-1);
}

/**********************************************************************
 * IMAPMailboxTitle - provide custom titles for special IMAP boxes.
 *	Do this for all non-Dominant IMAP mailboxes.
 **********************************************************************/
Boolean IMAPMailboxTitle(TOCHandle tocH, Str255 title)
{
	MailboxNodeHandle mbox;
	PersHandle pers;
	
	if (tocH && (*tocH)->imapTOC)
	{
		mbox = TOCToMbox(tocH);
		pers = TOCToPers(tocH);
		if (mbox && pers && (pers != PersList))
		{
			ComposeRString(title, IMAP_MAILBOXTITLE_FMT, (*tocH)->mailbox.spec.name, (*pers)->name);
			return (true);
		}	
	}
	return (false);
}

/**********************************************************************
 * IMAPMailboxExists - does this IMAP mailbox exist on the server?
 **********************************************************************/
Boolean IMAPMailboxExists(Str255 mailboxName)
{
	Boolean bExists = false;
	IMAPStreamPtr imapStream = nil;
	Str255 cName;
	
	// must be online to do this
	if (Offline && GoOnline()) return (nil);
			
	Zero(cName);
	BMD(mailboxName+1, cName, mailboxName[0]);
	
	// Set up to connect to the server
	if (imapStream = GetIMAPConnection(UndefinedTask, CAN_PROGRESS))
	{
		// fetch the attributes of the new mailbox to see if it exists
		if (FetchMailboxAttributes(imapStream, cName))
		{
			// the mailbox exists!  Add it to the tree
			if (imapStream->mailStream->fListResultsHandle)
			{
				ZapHandle(imapStream->mailStream->fListResultsHandle);
				bExists = true;
			}		
		}		
	}
	
	// clean up the conneciton we used to create the mailbox
	if (imapStream) CleanupConnection(&imapStream);
	
	return (bExists);	
}

/**********************************************************************
 * GetHiddenCacheMailbox - find and open the cache mailbox where
 *	deleted messages are stored.  Create it if needed.
 **********************************************************************/
TOCHandle GetHiddenCacheMailbox(MailboxNodeHandle mbox, Boolean bForce, Boolean bCreateIfNeeded)
{
	TOCHandle tocH = NULL;
	FSSpec hidSpec;
	Boolean bExists;
	
	if (mbox && (bForce || !DoesIMAPMailboxNeed(mbox,kShowDeleted)))
	{
		// figure out what the deleted cache would be called
		hidSpec = (*mbox)->mailboxSpec;
		GetRString(hidSpec.name,IMAP_HIDDEN_TOC_NAME);
		
		// does it already exist?
		bExists = (FSpExists(&hidSpec)==noErr);
		
		// create it if needed
		if (bCreateIfNeeded && !bExists)
		{
			if (FSpCreate(&hidSpec,CREATOR,IMAP_MAILBOX_TYPE,smSystemScript) == noErr)
				bExists = true;
		}	
		
		// open the mailbox
		if (bExists) tocH = TOCBySpec(&hidSpec);
		
		// and associate it with the mbox
		if (tocH) (*tocH)->imapMBH = mbox;
	}
	
	return (tocH);
}

/**********************************************************************
 * CleanHiddenCacheMailbox - clear out the hidden cache mailbox, 
 *	hopefully clearing it of any corrupted messages.
 **********************************************************************/
OSErr CleanHiddenCacheMailbox(TOCHandle hidTocH)
{
	OSErr err = noErr;
	FSSpec spec;
	short mNum;
	
	// something happened to our hidden cache mailbox.
	ASSERT(0);
	
	// was a toc specified?
	if (!hidTocH || !*hidTocH)
		return (paramErr);
	
	// is this really a hidden toc?
	spec = GetMailboxSpec(hidTocH, -1);
	if (EqualStrRes(spec.name, IMAP_HIDDEN_TOC_NAME))
	{
		// close and remove all of its messages
		for (mNum=(*hidTocH)->count-1;mNum>=0;mNum--)
		{
			if ((*hidTocH)->sums[mNum].messH)
				CloseMyWindow(GetMyWindowWindowPtr((*(*hidTocH)->sums[mNum].messH)->win));			
			DeleteIMAPSum(hidTocH,mNum);
		}
		
		// compact it to be sure to remove all leftovers
		CompactMailbox(&spec,false);
	}
	
	return (err);
}

/**********************************************************************
 * HideDeletedMessages - Hide/Show deleted messages in a mailbox.
 **********************************************************************/
OSErr HideDeletedMessages(MailboxNodeHandle mbox, Boolean bForce, Boolean bShow)
{
	TOCHandle tocH, hidTocH;
	short sumNum;
	OSErr err = noErr;
	
	// find the local cache of deleted messages
	hidTocH = GetHiddenCacheMailbox(mbox, bForce, !bShow);
	
	// open the mailbox we're interested in if needed
	LockMailboxNodeHandle(mbox);
	tocH = TOCBySpec(&(*mbox)->mailboxSpec);
	UnlockMailboxNodeHandle(mbox);
	
	// if we're showing but this mailbox doesn't have a deleted cache,
	// there's nothing to do.
	if (bShow && !hidTocH)
		return (noErr);
	
	// if no toc was found for this mailbox, or if we couldn't create the
	// deleted toc, error.
	if (!tocH || !hidTocH)
		return (fnfErr);
	
	// don't let these tocs go away
	IMAPTocHBusy(tocH, true);
	IMAPTocHBusy(hidTocH, true);
	
	if (bShow)
	{
		// move all summaries from the deleted cache to the real mailbox
		for (sumNum = (*hidTocH)->count - 1; (sumNum >= 0) && (err == noErr) ; --sumNum)
		{
			CycleBalls();
			err = IMAPTransferLocalCache(hidTocH, &((*hidTocH)->sums[sumNum]), tocH, (*hidTocH)->sums[sumNum].uidHash, false);
			DeleteIMAPSum(hidTocH, sumNum);
		}
	}
	else
	{
		// move all deleted messages from the mailbox to the deleted cache
		for (sumNum = (*tocH)->count-1; (sumNum >= 0) && (err == noErr)  ; sumNum--)
		{
			if ((*tocH)->sums[sumNum].opts&OPT_DELETED)
			{
				CycleBalls();
				err = IMAPTransferLocalCache(tocH, &((*tocH)->sums[sumNum]), hidTocH, (*tocH)->sums[sumNum].uidHash, false);
				DeleteIMAPSum(tocH, sumNum);
			}
		}
	}
	
	// if an error occurred writing to or reading from the hidden toc, recreate it.
	if (err != noErr)
		CleanHiddenCacheMailbox(hidTocH);
	
	// we're done with these tocs
	IMAPTocHBusy(tocH, false);
	IMAPTocHBusy(hidTocH, false);
	
	return (noErr);
}

/**********************************************************************
 * CountDeletedMessages - Count the number of deleted messages in a
 *	mailbox.  Consider the hidden toc as well, if appropriate.
 **********************************************************************/
short CountDeletedIMAPMessages(TOCHandle tocH)
{
	short count, sumNum;
	MailboxNodeHandle mbox = TOCToMbox(tocH);
	TOCHandle hidTocH;
	
	for (count=sumNum=0;sumNum<(*tocH)->count;sumNum++)
		if ((*tocH)->sums[sumNum].opts&OPT_DELETED) count++;
		
	// do we need to consider the hidden toc as well?
	hidTocH = GetHiddenCacheMailbox(mbox, false, false);
	for (sumNum=0;hidTocH && (sumNum<(*hidTocH)->count);sumNum++)
		if ((*hidTocH)->sums[sumNum].opts&OPT_DELETED) count++;
		
	return (count);
}

/**********************************************************************
 * HideShowSummary - make sure a summary is in the right toc.  If it's
 *	not, move it.  Return true if the summary is shown.
 **********************************************************************/
Boolean HideShowSummary(TOCHandle toc, TOCHandle tocH, TOCHandle hidTocH, short sumNum)
{
	Boolean bShown = false;
	Boolean bDeleted;
	OSErr err = noErr;
	
	// must have a source toc, a visible toc and a hidden toc
	if (toc && tocH && hidTocH)	
	{
		// is this summary marked as deleted?
		bDeleted = ((*toc)->sums[sumNum].opts&OPT_DELETED)!=0;	
		
		// is this a deleted summary in the visible toc?
		if (bDeleted && (toc == tocH))
		{
			// move it to the hidden toc
			err = IMAPTransferLocalCache(tocH, &((*tocH)->sums[sumNum]), hidTocH, (*tocH)->sums[sumNum].uidHash, false);
			DeleteIMAPSum(tocH, sumNum);
		}
		// is this a non deleted summary in the hidden toc?
		else if (!bDeleted)
		{
			if (toc == hidTocH)
			{
				// move it to the visible toc
				err = IMAPTransferLocalCache(hidTocH, &((*hidTocH)->sums[sumNum]), tocH, (*hidTocH)->sums[sumNum].uidHash, false);
				DeleteIMAPSum(hidTocH, sumNum);
			}
			bShown = true;
		}
		// else
			// the summary is in the right toc already.
			
		// was there a problem with the hidden toc?  Recreate it.
		if (err) 
			CleanHiddenCacheMailbox(hidTocH);
	}
	return (bShown);
}

/**********************************************************************
 * ShowHideFilteredSummary - move a filtered IMAP summary to the proper
 *	toc.  Returns true if the summary was moved.
 **********************************************************************/
Boolean ShowHideFilteredSummary(TOCHandle toc, short sumNum)
{
 	Boolean bMoved = false;
 	MailboxNodeHandle mbox;
 	TOCHandle tocH, hidTocH;
 	short junkThresh = GetRLong(JUNK_MAILBOX_THRESHHOLD);
 	Boolean bIsJunk;
 	TOCHandle junkTocH = LocateIMAPJunkToc(toc, false, true);
 	
 	if (toc && *toc)
 	{
 		mbox = TOCToMbox(toc);
 		if (mbox)
 		{
 			// find the real toc ...
 			LockMailboxNodeHandle(mbox);
 			tocH = TOCBySpec(&(*mbox)->mailboxSpec);
 			UnlockMailboxNodeHandle(mbox);
 			
 			// is there a hidden mailbox?
 			if (DoesIMAPMailboxNeed(mbox,kShowDeleted))
 				return (false);
 			
 			// locate the hidden toc as well ...
 			hidTocH = GetHiddenCacheMailbox(mbox, false, false);
 			
 			// skip junked messages
			bIsJunk = (junkTocH && HasFeature(featureJunk) && ((*toc)->sums[sumNum].spamScore >= junkThresh));
			
			if (toc ==  hidTocH)
			{
				// only move non-deleted, filtered, not junk messages to the visible toc.
				if ((((*hidTocH)->sums[sumNum].opts&OPT_DELETED) == 0)		// not deleted
				 && (((*hidTocH)->sums[sumNum].flags&FLAG_UNFILTERED) == 0)	// already filtered
				 && !bIsJunk)												// not about to be filtered to Junk
					bMoved = HideShowSummary(toc, tocH, hidTocH, sumNum);	
			}
			else
			{
				// move all appropriate messages to the invisible toc
				bMoved = !HideShowSummary(toc, tocH, hidTocH, sumNum);		
			}
 		}
 	}
 	return (bMoved);
 }

/**********************************************************************
 * GetRealIMAPSpec - given a tocH, either hidden or visible, return
 *	the appropriate IMAP mailbox cache spec
 *
 *	Warning - this routine hits the disk.
 **********************************************************************/
MailboxNodeHandle GetRealIMAPSpec(FSSpec orig, FSSpecPtr spec)
{
	MailboxNodeHandle node = NULL;	// initialize
	TOCHandle tocH;
	PersHandle pers;
	CInfoPBRec pb;
	OSErr err;
	
	// initialize
	*spec = orig;

	// don't do anything if it doesn't have the right name
	if (EqualStrRes(orig.name, IMAP_HIDDEN_TOC_NAME))
	{	
		// first, see if this is already open
		tocH = FindTOC(&orig);
		if (tocH)
		{
			// it is!  This is easy.
			node = TOCToMbox(tocH);
			if (node)
				*spec = (*node)->mailboxSpec;
		}
		else
		{
			// nope, we're going to have to poke around in the file system to find out
			
			// The real IMAP cache folder has the same name as the enclosing folder
			// determine the name of the enclosing folder
			Zero(pb);
			pb.dirInfo.ioNamePtr = spec->name;
			pb.dirInfo.ioVRefNum = orig.vRefNum;
			pb.dirInfo.ioDrDirID = orig.parID;
			pb.dirInfo.ioFDirIndex = -1;		/* use ioDirID */
			err = PBGetCatInfoSync(&pb);
			if (!err)
			{
				// is this an IMAP mailbox file?		
				LocateNodeBySpecInAllPersTrees(spec, &node, &pers);
			}
		}
		
		// did we fail to find the real TOC?
		if (node == NULL)
			spec->name[0] = 0;
	}
	
	return (node);
}

/**********************************************************************
 * EnsureSpecialMailboxes - make sure the user has selected
 *	a Junk and a Trash mailbox if required.
 **********************************************************************/
Boolean EnsureSpecialMailboxes(PersHandle pers)
{	
	Boolean bGotBox;
	
	// if there's no mailbox tree at all, then we can't do any of this.
	// assume the caller is smart enough to know to get the mailbox tree
	if (!MailboxTreeGood(pers))
		return (true);
	
	PushPers(pers);
	CurPers = pers;
	
	// ask the user for the trash mailbox
	bGotBox = PrefIsSet(PREF_IMAP_NO_FANCY_TRASH) || (GetIMAPTrashMailbox(CurPers, true, true) != NULL);
	if (!bGotBox)
	{
		// user has refused to pick a trash mailbox.  Offer to turn off FTM.
		if (ComposeStdAlert(Stop, IMAP_MISSING_TRASH, (*pers)->name)==2)
		{
			SetPref(PREF_IMAP_NO_FANCY_TRASH, YesStr); 	
			bGotBox = true;
		}
	}
	
	if (bGotBox)
	{
		// ask the user for the junk mailbox
		bGotBox = JunkPrefCantCreateJunk() || JunkPrefIMAPNoRunPlugins() || (GetIMAPJunkMailbox(CurPers, true, true) != NULL);
		if (!bGotBox)
		{
			// user has refused to pick a junk mailbox.  Offter to turn off Junk.
			if (ComposeStdAlert(Stop, IMAP_MISSING_JUNK, (*pers)->name)==2)
			{
				SetPrefLong(PREF_JUNK_MAILBOX,GetPrefLong(PREF_JUNK_MAILBOX)|bJunkPrefIMAPNoRunPlugins);
				bGotBox = true;
			}
		}	
	}

	PopPers();

	// warn the user about IMAP Auto-Expunge, if appropriate
	IMAPAutoExpungeWarning();
	
	return (bGotBox);
}

/**********************************************************************
 * IMAPAutoExpungeWarning - warn the user about autoexpunge.  
 *	Do this once, and ONLY if the user has an IMAP personality set
 *	up currently with the autoexpunge pref set.
 **********************************************************************/
void IMAPAutoExpungeWarning(void)
{
	PersHandle pers;
	Boolean bWarn = false;
	
	PushPers(CurPers);
	
	// have we warned before?
	CurPers = PersList;
	if (!IMAPAutoExpungeWarned())
	{
		// Are there any IMAP personalities defined?
		for (pers = PersList; pers && !bWarn; pers = (*pers)->next)
		{
			CurPers = pers;
			bWarn = PrefIsSet(PREF_IS_IMAP) && !IMAPAutoExpungeDisabled();
		}

		if (bWarn)
		{
			// disable auto-expunge	
			if (ComposeStdAlert(Caution,IMAP_AUTOEXPUNGE_WARNING) == kAlertStdAlertCancelButton)
			{
				for (pers = PersList; pers; pers = (*pers)->next)
				{
					CurPers = pers;
					if (PrefIsSet(PREF_IS_IMAP))
					{
						// reset auto-expunge preference
						SetPrefLong(PREF_IMAP_AUTOEXPUNGE,bIMAPAutoExpungeDisabled|bIMAPAutoExpungeNoThreshold);
					}
				}
			}
		}
		
		// either way, don't warn again
		CurPers = PersList;
		SetPrefLong(PREF_IMAP_AUTOEXPUNGE,GetPrefLong(PREF_IMAP_AUTOEXPUNGE)|bIMAPAutoExpungeWarned);
	}
	PopPers();
}

/**********************************************************************
 * IMAPAutoExpungeMailbox - check to see if this mailbox needs to be
 *	automatically expunged.
 **********************************************************************/
Boolean IMAPAutoExpungeMailbox(TOCHandle tocH)
{
	Boolean bNeeds = false;
	FSSpec spec;
	
	// are we quitting?
	if (AmQuitting)
		return (false);
	
	// are we offline?
	if (Offline)
		return (false);
	
	// is filtering or someother background thread currently underway?
	if (GetNumBackgroundThreads() || IMAPFilteringUnderway())
		return (false);
	
	// is this an IMAP toc?
	if (tocH && (*tocH)->imapTOC)
	{
		// make sure this isn't a hidden cache mailbox.  We should ignore those.
		spec = (*tocH)->mailbox.spec;
		if (!EqualStrRes(spec.name, IMAP_HIDDEN_TOC_NAME))
		{
			// is the auto expunge preference set?
			PushPers(CurPers);
			if (!IMAPAutoExpungeDisabled())
			{
				// then expunge it if the threshold has been reached
				bNeeds = MarkOrExpungeMailboxIfNeeded(TOCToMbox(tocH), NULL, true);
			}
			PopPers();	
		}
	}
	
	return (bNeeds);
}

/**********************************************************************
 * IMAPAutoExpunge - periodic check to see if we need to perform any
 *	automatic IMAP mailboxe EXPUNGEs.  Return TRUE if we end up 
 *	starting an EXPUNGE command.
 **********************************************************************/
Boolean IMAPAutoExpunge(void)
{
	static long throttle = 0;
	Boolean bPerformed = false;
	FSSpec spec;
	FSSpecHandle mailboxes;
	OSErr err;
	
	// don't do anything if there's a background thread runing, 
	// if filtering is underway or if we're offline.
	if (GetNumBackgroundThreads() || IMAPFilteringUnderway() || Offline)
		return (false);
	
	// do this once early on, then no more than about once an hour
	if ((throttle == 0) || ((TickCount() - throttle) > 60*60*60))	// once an hour
	{
		PushPers(nil);
		for (CurPers=PersList;CurPers;CurPers=(*CurPers)->next)
		{
			// is this personality set to automatically expunge IMAP mailboxes?
			if (PrefIsSet(PREF_IS_IMAP) && !IMAPAutoExpungeDisabled())
			{
				// find the next mailbox to expunge ...
				LDRef(CurPers);
				if (GetNextMailboxToExpunge((*CurPers)->mailboxTree, &spec))
				{
					mailboxes = NuHandle(sizeof(FSSpec));
					if (mailboxes)
					{
						*((FSSpec *)(*mailboxes)) = spec;
						err = IMAPProcessMailboxes(mailboxes, IMAPMultExpungeTask);
			
						// did we successfully start some EXPUNGEs?
						if (err == noErr)
							bPerformed = true;
						else
							ZapHandle(mailboxes);	
					}
				}
				UL(CurPers);
			}
			
			// let this expunge finish before we start another.
			if (bPerformed)
			{
				ComposeLogS(LOG_MOVE,nil,"\pAuto-expunging mailbox %p",spec.name);
				break;
			}
		}
		PopPers();
		
		// don't do this again for a while if we've handled all of the mailboxes
		if (!bPerformed)
			throttle = TickCount();
	}
	
	return (bPerformed);
}

/**********************************************************************
 * GetNextMailboxToExpunge - find the next mailbox for this personality
 *	that needs to be expunged
 **********************************************************************/
Boolean GetNextMailboxToExpunge(MailboxNodeHandle tree, FSSpec *spec)
{
	Boolean bFound = false;
	MailboxNodeHandle scan = tree;
	
	while (scan)
	{
		// does this mailbox need to be expunged?
		bFound = MarkOrExpungeMailboxIfNeeded(scan, spec, false);
		
		// Check the children
		if (!bFound)
		{
			LockMailboxNodeHandle(scan);
			if ((*scan)->childList) bFound = GetNextMailboxToExpunge(((*scan)->childList), spec);
			UnlockMailboxNodeHandle(scan);
		}
		
		if (bFound)
			break;
		
		// go to the next node.
		scan = (*scan)->next;
	}
	
	return (bFound);
}

/**********************************************************************
 * ExpungeMailboxIfNeeded - expunge a mailbox if needed.  Return TRUE
 *	and the spec of the mailbox if expunge is needed.
 *
 *	ASSUMES CURPERS = IMAP PERS
 *	NOT THREAD SAFE
 **********************************************************************/
Boolean MarkOrExpungeMailboxIfNeeded(MailboxNodeHandle mbox, FSSpec *spec, Boolean bNow)
{
	Boolean bNeeds = false;
	TOCHandle tocH;
	short numDeleted, numTotal;
	
	ASSERT(!InAThread());
	
	// might this mailbox need to be expunged?
	if (DoesIMAPMailboxNeed(mbox, kNeedsAutoExp)) 
	{
		// does this mailbox really need to be expunged now?
		LockMailboxNodeHandle(mbox);
		tocH = TOCBySpec(&(*mbox)->mailboxSpec);
		UnlockMailboxNodeHandle(mbox);
		
		if (tocH)
		{
			// count the number of deleted messages
			numDeleted = CountDeletedIMAPMessages(tocH);
				
			// check threshold only if the pref is set to ...
			if (IMAPAutoExpungeAlways())
				bNeeds = (numDeleted != 0);	
			else
			{	
				// how many total messages are there?
				numTotal = (*tocH)->count;
				if (!DoesIMAPMailboxNeed(mbox, kShowDeleted))
					numTotal += numDeleted;
				
				// are we above the threshold?
				if (numTotal && ((numDeleted * 100)/numTotal >= GetRLong(IMAP_AUTOEXPUNGE_THRESHOLD)))
					bNeeds = true;
			}
			
			if (bNeeds)
			{
				// return the spec if requested ...
				if (spec)
					*spec = (*mbox)->mailboxSpec;
				
				// expunge right now if we ought to
				if (bNow) 
				{
					IMAPRemoveDeletedMessages(tocH);
					SetIMAPMailboxNeeds(mbox, kNeedsAutoExp, false);
				}
			}
			else
			{
				// we're not at the threshold yet.  Ignore this mailbox until
				// another delete happens in it.
				SetIMAPMailboxNeeds(mbox, kNeedsAutoExp, false);
			}
		}
	}
	
	return (bNeeds);
}

/**********************************************************************
 * MarkSumAsDeleted - mark an IMAP summary as deleted.  Set the auto
 *	expunge flag for the mailbox as well.
 **********************************************************************/
 void MarkSumAsDeleted(TOCHandle tocH, short sumNum, Boolean bDeleted)
 {	
 	if (bDeleted)
 	{
 		(*tocH)->sums[sumNum].opts |= OPT_DELETED;
 		SetIMAPMailboxNeeds(TOCToMbox(tocH), kNeedsAutoExp, true);
 	}
 	else
 		(*tocH)->sums[sumNum].opts &= ~OPT_DELETED;
 }
 
 /**********************************************************************
 * IMAPPersIDChanged - called when the persID of an existing personality
 *	changes due to a rename, for example
 **********************************************************************/
 void IMAPPersIDChanged(PersHandle pers, MailboxNodeHandle tree)
 {
	while (pers && tree)
	{
		// change the pers id of this mailbox
		(*tree)->persId = (*pers)->persId;
		
		// do the children
		if ((*tree)->childList) 
			IMAPPersIDChanged(pers, (*tree)->childList);
		
		// check the next node.
		tree = (*tree)->next;
	}
 }