eudora-mac/imapmailboxes.c

1 line
127 KiB
C
Executable File

/* 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;
}
}