Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

10249 lines
347 KiB

//***************************************************************************
// IMAP4 Message Sync Class Implementation (CIMAPSync)
// Written by Raymond Cheng, 5/5/98
// Copyright (C) Microsoft Corporation, 1998
//***************************************************************************
//---------------------------------------------------------------------------
// Includes
//---------------------------------------------------------------------------
#include "pch.hxx"
#include "imapsync.h"
#include "xputil.h"
#include "flagconv.h"
#include "imapute.h"
#include "storutil.h"
#include "xpcomm.h"
#include "iert.h"
#include "menures.h"
#include "serverq.h"
#include "instance.h"
#include "demand.h"
#include "storecb.h"
#define USE_QUEUING_LAYER
//---------------------------------------------------------------------------
// Module Data Types
//---------------------------------------------------------------------------
typedef struct tagFOLDERIDLIST
{
FOLDERID idFolder;
struct tagFOLDERIDLIST *pfilNextFolderID;
} FOLDERIDLISTNODE;
//---------------------------------------------------------------------------
// Module Constants
//---------------------------------------------------------------------------
#define CCHMAX_CMDLINE 512
#define CCHMAX_IMAPFOLDERPATH 512
#define CHASH_BUCKETS 50
static const char cszIMAPFetchNewHdrsI4[] = "%lu:* (RFC822.HEADER RFC822.SIZE UID FLAGS INTERNALDATE)";
static const char cszIMAPFetchNewHdrsI4r1[] =
"%lu:* (BODY.PEEK[HEADER.FIELDS (References X-Ref X-Priority X-MSMail-Priority X-MSOESRec Newsgroups)] "
"ENVELOPE RFC822.SIZE UID FLAGS INTERNALDATE)";
static const char cszIMAPFetchCachedFlags[] = "1:%lu (UID FLAGS)";
enum
{
tidDONT_CARE = 0, // Means that transaction ID is unimportant or unavailable
tidSELECTION,
tidFETCH_NEW_HDRS,
tidFETCH_CACHED_FLAGS,
tidCOPYMSGS, // The COPY command used to copy msgs to another IMAP fldr
tidMOVEMSGS, // The STORE command used to delete msg ranges - currently only used for moves
tidBODYMSN, // The FETCH command used to get MsgSeqNumToUID translation before tidBODY
tidBODY, // The FETCH command used to retrieve a msg body
tidNOOP, // The NOOP command used to poll for new messages
tidCLOSE,
tidSELECT,
tidUPLOADMSG, // The APPEND command used to upload a message to IMAP server
tidMARKMSGS,
tidCREATE, // the CREATE cmd sent to create a folder
tidCREATELIST, // the LIST command sent after a CREATE
tidCREATESUBSCRIBE, // the SUBSCRIBE command sent after a CREATE
tidHIERARCHYCHAR_LIST_B, // the LIST cmd sent to find hierarchy char (Plan B)
tidHIERARCHYCHAR_CREATE, // the CREATE cmd sent to find hierarchy char
tidHIERARCHYCHAR_LIST_C, // the LIST cmd sent to find hierarchy char (Plan C)
tidHIERARCHYCHAR_DELETE, // the DELETE cmd sent to find hierarchy char
tidPREFIXLIST, // Prefixed hierarchy listing (eg, "~raych/Mail" prefix)
tidPREFIX_HC, // the LIST cmd sent to find hierarchy char for prefix
tidPREFIX_CREATE, // the CREATE cmd sent to create the prefix folder
tidDELETEFLDR, // the DELETE cmd sent to delete a folder
tidDELETEFLDR_UNSUBSCRIBE, // the UNSUBSCRIBE cmd sent to unsub a deleted fldr
tidRENAME, // The RENAME cmd sent to rename a folder
tidRENAMESUBSCRIBE, // The subscribe cmd sent to subscribe a folder
tidRENAMELIST, // The LIST cmd sent to check if rename was atomic
tidRENAMERENAME, // The second rename attempt, if server does non-atomic rename
tidRENAMESUBSCRIBE_AGAIN, // The subscribe cmd sent to attempt second new-tree subscribe again
tidRENAMEUNSUBSCRIBE, // The unsubscribe cmd sent to unsubscribe the old folders
tidSUBSCRIBE, // The (un)subscribe command sent to (un)subscribe a folder
tidSPECIALFLDRLIST, // The LIST command sent to check if a special folder exists
tidSPECIALFLDRLSUB, // The LSUB command sent to list a special folder's subscribed subfolders
tidSPECIALFLDRSUBSCRIBE, // The SUBSCRIBE command sent out to subscribe an existing special folder
tidFOLDERLIST,
tidFOLDERLSUB,
tidEXPUNGE, // EXPUNGE command
tidSTATUS, // STATUS command used for IMessageServer::GetFolderCounts
};
enum
{
fbpNONE, // Fetch Body Part identifier (lpFetchCookie1 is set to this)
fbpHEADER,
fbpBODY,
fbpUNKNOWN
};
// Priorities, for use with _EnqueueOperation
enum
{
uiTOP_PRIORITY, // Ensures that we construct MsgSeqNum table before ALL user operations
uiNORMAL_PRIORITY // Priority level for all user operations
};
// Argument Readability Defines
const BOOL DONT_USE_UIDS = FALSE; // For use with IIMAPTransport
const BOOL USE_UIDS = TRUE ; // For use with IIMAPTransport
const BOOL fUPDATE_OLD_MSGFLAGS = TRUE; // For use with DownloadNewHeaders
const BOOL fDONT_UPDATE_OLD_MSGFLAGS = FALSE; // For use with DownloadNewHeaders
const BOOL fCOMPLETED = 1; // For use with NotifyMsgRecipients
const BOOL fPROGRESS = 0; // For use with NotifyMsgRecipients
const BOOL fLOAD_HC = FALSE; // (LoadSaveRootHierarchyChar): Load hierarchy character from foldercache
const BOOL fSAVE_HC = TRUE; // (LoadSaveRootHierarchyChar): Save hierarchy character to foldercache
const BOOL fHCF_PLAN_A_ONLY = TRUE; // Only execute Plan A in hierarchy char determination
const BOOL fHCF_ALL_PLANS = FALSE; // Execute Plans A, B, C and Z in hierarchy char determination
const BOOL fSUBSCRIBE = TRUE; // For use with SubscribeToFolder
const BOOL fUNSUBSCRIBE = FALSE; // For use with SubscribeToFolder
const BOOL fRECURSIVE = TRUE; // For use with DeleteFolderFromCache
const BOOL fNON_RECURSIVE = FALSE; // For use with DeleteFolderFromCache
const BOOL fINCLUDE_RENAME_FOLDER = TRUE; // For use with RenameTreeTraversal
const BOOL fEXCLUDE_RENAME_FOLDER = FALSE; // For use with RenameTreeTraversal
const BOOL fREMOVE = TRUE; // For use with IHashTable::Find
const BOOL fNOPROGRESS = FALSE; // For use with CStoreCB::Initialize
#define pahfoDONT_CREATE_FOLDER NULL // For use with FindHierarchalFolderName
const HRESULT S_CREATED = 1; // Indicates FindHierarchicalFolderName created the fldr
// None of the following bits can be set for a message to qualify as "unread"
const DWORD dwIMAP_UNREAD_CRITERIA = IMAP_MSG_SEEN | IMAP_MSG_DELETED;
// Internal flags for use with m_dwSyncToDo
const DWORD SYNC_FOLDER_NOOP = 0x80000000;
const DWORD AFTC_SUBSCRIBED = 0x00000001; // For use with AddToFolderCache's dwATFCFlags
const DWORD AFTC_KEEPCHILDRENKNOWN = 0x00000002; // For use with AddToFolderCache's dwATFCFlags
const DWORD AFTC_NOTSUBSCRIBED = 0x00000004; // For use with AddToFolderCache's dwATFCFlags
const DWORD AFTC_NOTRANSLATION = 0x00000008; // For use with AddToFolderCache's dwATFCFlags
#define AssertSingleThreaded AssertSz(m_dwThreadId == GetCurrentThreadId(), "The IMAPSync is not multithreaded. Someone is calling me on multiple threads")
const DWORD snoDO_NOT_DISPOSE = 0x00000001; // For use with _SendNextOperation
// Connection FSM
const UINT WM_CFSM_EVENT = WM_USER;
//---------------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------------
//***************************************************************************
//***************************************************************************
HRESULT CreateImapStore(IUnknown *pUnkOuter, IUnknown **ppUnknown)
{
CIMAPSync *pIMAPSync;
HRESULT hr;
TraceCall("CIMAPSync::CreateImapStore");
IxpAssert(NULL != ppUnknown);
// Initialize return values
*ppUnknown = NULL;
hr = E_NOINTERFACE;
if (NULL != pUnkOuter)
{
hr = TraceResult(CLASS_E_NOAGGREGATION);
goto exit;
}
pIMAPSync = new CIMAPSync;
if (NULL == pIMAPSync)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
#ifdef USE_QUEUING_LAYER
hr = CreateServerQueue(pIMAPSync, (IMessageServer **)ppUnknown);
pIMAPSync->Release(); // Since we're not returning this ptr, bump down refcount
#else
// If we reached this point, everything is working great
*ppUnknown = SAFECAST(pIMAPSync, IMessageServer *);
hr = S_OK;
#endif
exit:
// Done
return hr;
}
//***************************************************************************
// Function: CIMAPSync (constructor)
//***************************************************************************
CIMAPSync::CIMAPSync(void)
{
TraceCall("CIMAPSync::CIMAPSync");
m_cRef = 1;
m_pTransport = NULL;
ZeroMemory(&m_rInetServerInfo, sizeof(m_rInetServerInfo));
m_idFolder = FOLDERID_INVALID;
m_idSelectedFolder = FOLDERID_INVALID;
m_idIMAPServer = FOLDERID_INVALID;
m_pszAccountID = NULL;
m_szAccountName[0] = '\0';
m_pszFldrLeafName = NULL;
m_pStore = NULL;
m_pFolder = NULL;
m_pDefCallback = NULL;
m_pioNextOperation = NULL;
m_dwMsgCount = 0;
m_fMsgCountValid = FALSE;
m_dwNumNewMsgs = 0;
m_dwNumHdrsDLed = 0;
m_dwNumUnreadDLed = 0;
m_dwNumHdrsToDL = 0;
m_dwUIDValidity = 0;
m_dwSyncFolderFlags = 0;
m_dwSyncToDo = 0;
m_lSyncFolderRefCount = 0;
m_dwHighestCachedUID = 0;
m_rwsReadWriteStatus = rwsUNINITIALIZED;
m_fCreateSpecial = TRUE;
m_fNewMail = FALSE;
m_fInbox = FALSE;
m_fDidFullSync = FALSE;
m_csNewConnState = CONNECT_STATE_DISCONNECT;
m_cRootHierarchyChar = INVALID_HIERARCHY_CHAR;
m_phcfHierarchyCharInfo = NULL;
m_fReconnect = FALSE;
m_issCurrent = issNotConnected;
m_szRootFolderPrefix[0] = '\0';
m_fPrefixExists = FALSE;
// Central repository
m_pCurrentCB = NULL;
m_sotCurrent = SOT_INVALID;
m_idCurrent = FOLDERID_INVALID;
m_fSubscribe = FALSE;
m_pCurrentHash = NULL;
m_pListHash = NULL;
m_fTerminating = FALSE;
m_fInited = 0;
m_fDisconnecting = 0;
m_cFolders = 0;
m_faStream = 0;
m_pstmBody = NULL;
m_idMessage = 0;
m_fGotBody = FALSE;
m_cfsState = CFSM_STATE_IDLE;
m_cfsPrevState = CFSM_STATE_IDLE;
m_hwndConnFSM = NULL;
m_hrOperationResult = OLE_E_BLANK; // Uninitialized state
m_szOperationProblem[0] = '\0';
m_szOperationDetails[0] = '\0';
m_dwThreadId = GetCurrentThreadId();
}
//***************************************************************************
// Function: ~CIMAPSync (destructor)
//***************************************************************************
CIMAPSync::~CIMAPSync(void)
{
TraceCall("CIMAPSync::~CIMAPSync");
IxpAssert(0 == m_cRef);
if (NULL != m_phcfHierarchyCharInfo)
delete m_phcfHierarchyCharInfo;
ZeroMemory(&m_rInetServerInfo, sizeof(m_rInetServerInfo)); // Done for security
IxpAssert (!IsWindow(m_hwndConnFSM));
SafeMemFree(m_pszAccountID);
SafeMemFree(m_pszFldrLeafName);
SafeRelease(m_pStore);
SafeRelease(m_pFolder);
}
HRESULT CIMAPSync::QueryInterface(REFIID iid, void **ppvObject)
{
HRESULT hr;
TraceCall("CIMAPSync::QueryInterface");
AssertSingleThreaded;
IxpAssert(m_cRef > 0);
IxpAssert(NULL != ppvObject);
// Init variables, arguments
hr = E_NOINTERFACE;
if (NULL == ppvObject)
goto exit;
*ppvObject = NULL;
// Find a ptr to the interface
if (IID_IUnknown == iid)
*ppvObject = (IMessageServer *) this;
else if (IID_IMessageServer == iid)
*ppvObject = (IMessageServer *) this;
else if (IID_ITransportCallback == iid)
*ppvObject = (ITransportCallback *) this;
else if (IID_ITransportCallbackService == iid)
*ppvObject = (ITransportCallbackService *) this;
else if (IID_IIMAPCallback == iid)
*ppvObject = (IIMAPCallback *) this;
else if (IID_IIMAPStore == iid)
*ppvObject = (IIMAPStore *) this;
// If we returned an interface, return success
if (NULL != *ppvObject)
{
hr = S_OK;
((IUnknown *) *ppvObject)->AddRef();
}
exit:
return hr;
}
ULONG CIMAPSync::AddRef(void)
{
TraceCall("CIMAPSync::AddRef");
AssertSingleThreaded;
IxpAssert(m_cRef > 0);
m_cRef += 1;
DOUT ("CIMAPSync::AddRef, returned Ref Count=%ld", m_cRef);
return m_cRef;
}
ULONG CIMAPSync::Release(void)
{
TraceCall("CIMAPSync::Release");
AssertSingleThreaded;
IxpAssert(m_cRef > 0);
m_cRef -= 1;
DOUT("CIMAPSync::Release, returned Ref Count = %ld", m_cRef);
if (0 == m_cRef)
{
delete this;
return 0;
}
else
return m_cRef;
}
//===========================================================================
// IMessageSync Methods
//===========================================================================
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::Initialize(IMessageStore *pStore, FOLDERID idStoreRoot, IMessageFolder *pFolder, FOLDERID idFolder)
{
HRESULT hr;
BOOL fResult;
WNDCLASSEX wc;
TraceCall("CIMAPSync::Initialize");
AssertSingleThreaded;
if (pStore == NULL || idStoreRoot == FOLDERID_INVALID)
{
hr = TraceResult(E_INVALIDARG);
goto exit;
}
// check to make sure we're not inited twice.
if (m_fInited)
{
hr = TraceResult(CO_E_ALREADYINITIALIZED);
goto exit;
}
// Save current folder data
m_idIMAPServer = idStoreRoot;
m_idFolder = idFolder;
ReplaceInterface(m_pStore, pStore);
ReplaceInterface(m_pFolder, pFolder);
LoadLeafFldrName(idFolder);
hr = _LoadAccountInfo();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = _LoadTransport();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Create a window to queue Connection FSM messages
wc.cbSize = sizeof(WNDCLASSEX);
fResult = GetClassInfoEx(g_hInst, c_szIMAPSyncCFSMWndClass, &wc);
if (FALSE == fResult)
{
ATOM aResult;
// Register this window class
wc.style = 0;
wc.lpfnWndProc = CIMAPSync::_ConnFSMWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = g_hInst;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = c_szIMAPSyncCFSMWndClass;
wc.hIconSm = NULL;
aResult = RegisterClassEx(&wc);
if (0 == aResult && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
{
hr = TraceResult(E_FAIL);
goto exit;
}
}
m_hwndConnFSM = CreateWindowEx(WS_EX_TOPMOST, c_szIMAPSyncCFSMWndClass,
c_szIMAPSyncCFSMWndClass, WS_POPUP, 1, 1, 1, 1, NULL, NULL, g_hInst,
(LPVOID)this);
if (NULL == m_hwndConnFSM)
{
hr = TraceResult(E_FAIL);
goto exit;
}
// flag successful initialization
m_fInited = TRUE;
exit:
return hr;
}
HRESULT CIMAPSync::ResetFolder(IMessageFolder *pFolder, FOLDERID idFolder)
{
TraceCall("CIMAPSync::ResetFolder");
Assert(m_cRef > 0);
m_idFolder = idFolder;
ReplaceInterface(m_pFolder, pFolder);
LoadLeafFldrName(idFolder);
return S_OK;
}
void CIMAPSync::LoadLeafFldrName(FOLDERID idFolder)
{
FOLDERINFO fiFolderInfo;
SafeMemFree(m_pszFldrLeafName);
if (FOLDERID_INVALID != idFolder)
{
HRESULT hr;
hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
if (SUCCEEDED(hr))
{
m_pszFldrLeafName = PszDupA(fiFolderInfo.pszName);
if (NULL == m_pszFldrLeafName)
{
TraceResult(E_OUTOFMEMORY);
m_pszFldrLeafName = PszDupA(""); // If this fails, tough luck
}
m_pStore->FreeRecord(&fiFolderInfo);
}
}
}
HRESULT CIMAPSync::Close(DWORD dwFlags)
{
HRESULT hrTemp;
BOOL fCancelOperation = FALSE;
STOREERROR seErrorInfo;
IStoreCallback *pCallback = NULL;
STOREOPERATIONTYPE sotCurrent;
TraceCall("CIMAPSync::Close");
AssertSingleThreaded;
// validate flags
if (0 == (dwFlags & (MSGSVRF_HANDS_OFF_SERVER | MSGSVRF_DROP_CONNECTION)))
return TraceResult(E_UNEXPECTED);
// Check if we are to cancel the current operation
if (SOT_INVALID != m_sotCurrent &&
(dwFlags & (MSGSVRF_DROP_CONNECTION | MSGSVRF_HANDS_OFF_SERVER)))
{
fCancelOperation = TRUE;
if (NULL != m_pCurrentCB)
{
IxpAssert(SOT_INVALID != m_sotCurrent);
FillStoreError(&seErrorInfo, STORE_E_OPERATION_CANCELED, 0,
MAKEINTRESOURCE(IDS_IXP_E_USER_CANCEL), NULL);
// Remember how to call callback
pCallback = m_pCurrentCB;
sotCurrent = m_sotCurrent;
}
// Reset current operation vars
m_hrOperationResult = OLE_E_BLANK;
m_sotCurrent = SOT_INVALID;
m_pCurrentCB = NULL;
m_cfsState = CFSM_STATE_IDLE;
m_cfsPrevState = CFSM_STATE_IDLE;
m_fTerminating = FALSE;
// Clear the Connection FSM event queue
if (IsWindow(m_hwndConnFSM))
{
MSG msg;
while (PeekMessage(&msg, m_hwndConnFSM, WM_CFSM_EVENT, WM_CFSM_EVENT, PM_REMOVE))
{
TraceInfoTag(TAG_IMAPSYNC,
_MSG("CIMAPSync::Close removing WM_CFSM_EVENT, cfeEvent = %lX",
msg.wParam));
}
}
}
// If connection still exists, perform purge-on-exit and disconnect us as required
// Connection might not exist, however (eg, if modem connection terminated)
if (dwFlags & MSGSVRF_DROP_CONNECTION || dwFlags & MSGSVRF_HANDS_OFF_SERVER)
{
if (m_pTransport)
{
m_fDisconnecting = TRUE;
m_pTransport->DropConnection();
}
}
SafeRelease(m_pCurrentHash);
SafeRelease(m_pListHash);
SafeRelease(m_pstmBody);
if (dwFlags & MSGSVRF_HANDS_OFF_SERVER)
{
SafeRelease(m_pDefCallback);
FlushOperationQueue(issNotConnected, STORE_E_OPERATION_CANCELED);
if (IsWindow(m_hwndConnFSM))
{
if (m_dwThreadId == GetCurrentThreadId())
SideAssert(DestroyWindow(m_hwndConnFSM));
else
SideAssert(PostMessage(m_hwndConnFSM, WM_CLOSE, 0, 0));
}
// Let go of our transport object
if (m_pTransport)
{
m_pTransport->HandsOffCallback();
m_pTransport->Release();
m_pTransport = NULL;
}
m_fInited = 0;
}
// Notify caller that we're complete
if (fCancelOperation && NULL != pCallback)
{
HRESULT hrTemp;
hrTemp = pCallback->OnComplete(sotCurrent, seErrorInfo.hrResult, NULL, &seErrorInfo);
TraceError(hrTemp);
pCallback->Release();
}
// *** WARNING: After this point, OnComplete may have been called which may cause
// us to have been re-entered. Make no reference to module vars!
return S_OK;
}
HRESULT CIMAPSync::PurgeMessageProgress(HWND hwndParent)
{
CStoreCB *pCB = NULL;
HRESULT hrResult = S_OK;
TraceCall("CIMAPSync::PurgeMessageProgress");
// Check if we're connected and selected
if (NULL == m_pTransport || issSelected != m_issCurrent ||
FOLDERID_INVALID == m_idSelectedFolder || m_idSelectedFolder != m_idFolder ||
CFSM_STATE_IDLE != m_cfsState)
{
// Not in proper state to issue CLOSE
goto exit;
}
pCB = new CStoreCB;
if (NULL == pCB)
{
hrResult = TraceResult(E_OUTOFMEMORY);
goto exit;
}
hrResult = pCB->Initialize(hwndParent, MAKEINTRESOURCE(idsPurgingMessages), fNOPROGRESS);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
// Issue the CLOSE command
hrResult = _EnqueueOperation(tidCLOSE, 0, icCLOSE_COMMAND, NULL, uiNORMAL_PRIORITY);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
hrResult = _BeginOperation(SOT_PURGING_MESSAGES, pCB);
if (FAILED(hrResult) && E_PENDING != hrResult)
{
TraceResult(hrResult);
goto exit;
}
// Wait until CLOSE is complete
hrResult = pCB->Block();
TraceError(hrResult);
// Shut down
hrResult = pCB->Close();
TraceError(hrResult);
exit:
SafeRelease(pCB);
return hrResult;
}
HRESULT CIMAPSync::_ConnFSM_HandleEvent(CONN_FSM_EVENT cfeEvent)
{
HRESULT hrResult = S_OK;
IxpAssert(m_cRef > 0);
TraceCall("CIMAPSync::_HandleConnFSMEvent");
if (cfeEvent >= CFSM_EVENT_MAX)
{
hrResult = TraceResult(E_INVALIDARG);
goto exit;
}
if (m_cfsState >= CFSM_STATE_MAX)
{
hrResult = TraceResult(E_FAIL);
goto exit;
}
IxpAssert(NULL != c_pConnFSMEventHandlers[m_cfsState]);
hrResult = (this->*c_pConnFSMEventHandlers[m_cfsState])(cfeEvent);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
exit:
return hrResult;
} // _ConnFSM_HandleEvent
HRESULT CIMAPSync::_ConnFSM_Idle(CONN_FSM_EVENT cfeEvent)
{
HRESULT hrResult = S_OK;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_IDLE == m_cfsState);
TraceCall("CIMAPSync::_ConnFSM_Idle");
switch (cfeEvent)
{
case CFSM_EVENT_INITIALIZE:
// Don't need to do anything for this state
break;
case CFSM_EVENT_CMDAVAIL:
hrResult = _ConnFSM_ChangeState(CFSM_STATE_WAITFORCONN);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
break;
case CFSM_EVENT_ERROR:
// We don't care about no stinking errors (not in this state)
break;
default:
TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_Idle, got cfeEvent = %lu", cfeEvent));
hrResult = TraceResult(E_INVALIDARG);
break;
} // switch
exit:
return hrResult;
} // _ConnFSM_Idle
HRESULT CIMAPSync::_ConnFSM_WaitForConn(CONN_FSM_EVENT cfeEvent)
{
HRESULT hrResult = S_OK;
BOOL fAbort = FALSE;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_WAITFORCONN == m_cfsState);
TraceCall("CIMAPSync::_ConnFSM_WaitForConn");
switch (cfeEvent)
{
case CFSM_EVENT_INITIALIZE:
// We need to connect and authenticate. Do this even if we're already
// connected (we will check if user changed connection settings)
hrResult = SetConnectionState(CONNECT_STATE_CONNECT);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
break;
case CFSM_EVENT_CONNCOMPLETE:
hrResult = _ConnFSM_ChangeState(CFSM_STATE_WAITFORSELECT);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
break;
case CFSM_EVENT_ERROR:
fAbort = TRUE;
break;
default:
TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_WaitForConn, got cfeEvent = %lu", cfeEvent));
hrResult = TraceResult(E_INVALIDARG);
break;
} // switch
exit:
if (FAILED(hrResult) || fAbort)
{
HRESULT hrTemp;
// Looks like we're going to have to dump this operation
hrTemp = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
TraceError(hrTemp);
}
return hrResult;
} // _ConnFSM_WaitForConn
HRESULT CIMAPSync::_ConnFSM_WaitForSelect(CONN_FSM_EVENT cfeEvent)
{
HRESULT hrResult = S_OK;
BOOL fGoToNextState = FALSE;
BOOL fAbort = FALSE;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_WAITFORSELECT == m_cfsState);
TraceCall("CIMAPSync::_ConnFSM_WaitForSelect");
switch (cfeEvent)
{
case CFSM_EVENT_INITIALIZE:
// Do we need to SELECT the current folder for this operation?
if (_StoreOpToMinISS(m_sotCurrent) < issSelected)
{
// This operation does not require folder selection: ready to start operation
hrResult = _ConnFSM_ChangeState(CFSM_STATE_STARTOPERATION);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
}
else
{
// Issue the SELECT command for the current folder
hrResult = _EnsureSelected();
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
else if (STORE_S_NOOP == hrResult)
fGoToNextState= TRUE;
}
if (FALSE == fGoToNextState)
break;
// *** If fGoToNextState, FALL THROUGH ***
case CFSM_EVENT_SELECTCOMPLETE:
hrResult = _ConnFSM_ChangeState(CFSM_STATE_WAITFORHDRSYNC);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
break;
case CFSM_EVENT_ERROR:
fAbort = TRUE;
break;
default:
TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_WaitForSelect, got cfeEvent = %lu", cfeEvent));
hrResult = TraceResult(E_INVALIDARG);
break;
} // switch
exit:
if (FAILED(hrResult) || fAbort)
{
HRESULT hrTemp;
// Looks like we're going to have to dump this operation
hrTemp = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
TraceError(hrTemp);
}
return hrResult;
} // _ConnFSM_WaitForSelect
HRESULT CIMAPSync::_ConnFSM_WaitForHdrSync(CONN_FSM_EVENT cfeEvent)
{
HRESULT hrResult=S_OK;
BOOL fAbort = FALSE;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_WAITFORHDRSYNC == m_cfsState);
TraceCall("CIMAPSync::_ConnFSM_WaitForHdrSync");
switch (cfeEvent)
{
case CFSM_EVENT_INITIALIZE:
// Check if we're supposed to synchronize this folder
if (0 != m_dwSyncToDo)
{
// Yup, send the synchronization commands
Assert(0 == m_lSyncFolderRefCount);
m_lSyncFolderRefCount = 0;
hrResult = _SyncHeader();
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
}
else
// No synchronization requested
hrResult = STORE_S_NOOP;
// If no synchronization requested, fall through and proceed to next state
if (STORE_S_NOOP != hrResult)
break; // Our work here is done
// *** FALL THROUGH ***
case CFSM_EVENT_HDRSYNCCOMPLETE:
hrResult = _ConnFSM_ChangeState(CFSM_STATE_STARTOPERATION);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
break;
case CFSM_EVENT_ERROR:
fAbort = TRUE;
break;
default:
TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_WaitForHdrSync, got cfeEvent = %lu", cfeEvent));
hrResult = TraceResult(E_INVALIDARG);
break;
} // switch
exit:
if (FAILED(hrResult) || fAbort)
{
HRESULT hrTemp;
// Looks like we're going to have to dump this operation
hrTemp = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
TraceError(hrTemp);
}
return hrResult;
} // _ConnFSM_WaitForHdrSync
HRESULT CIMAPSync::_ConnFSM_StartOperation(CONN_FSM_EVENT cfeEvent)
{
HRESULT hrResult = S_OK;
BOOL fMoreCmdsToSend;
BOOL fAbort = FALSE;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_STARTOPERATION == m_cfsState);
TraceCall("CIMAPSync::_ConnFSM_StartOperation");
switch (cfeEvent)
{
case CFSM_EVENT_INITIALIZE:
// Launch operation
hrResult = _LaunchOperation();
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
else if (STORE_S_NOOP == hrResult)
{
// This means success, but no operation launched. Proceed directly to "DONE"
hrResult = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
}
else
{
// Proceed to the next state to wait for command completion
hrResult = _ConnFSM_ChangeState(CFSM_STATE_WAITFOROPERATIONDONE);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
}
break;
case CFSM_EVENT_ERROR:
fAbort = TRUE;
break;
default:
TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_StartOperation, got cfeEvent = %lu", cfeEvent));
hrResult = TraceResult(E_INVALIDARG);
break;
} // switch
exit:
if (FAILED(hrResult) || fAbort)
{
HRESULT hrTemp;
// Looks like we're going to have to dump this operation
hrTemp = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
TraceError(hrTemp);
}
return hrResult;
} // _ConnFSM_StartOperation
HRESULT CIMAPSync::_ConnFSM_WaitForOpDone(CONN_FSM_EVENT cfeEvent)
{
HRESULT hrResult = S_OK;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_WAITFOROPERATIONDONE == m_cfsState);
TraceCall("CIMAPSync::_ConnFSM_WaitForOpDone");
switch (cfeEvent)
{
case CFSM_EVENT_INITIALIZE:
// No need to do anything for initialization
break;
case CFSM_EVENT_OPERATIONCOMPLETE:
case CFSM_EVENT_ERROR:
// Proceed to next state
hrResult = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
break;
default:
TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_WaitForOpDone, got cfeEvent = %lu", cfeEvent));
hrResult = TraceResult(E_INVALIDARG);
break;
} // switch
exit:
return hrResult;
} // _ConnFSM_WaitForOpDone
HRESULT CIMAPSync::_ConnFSM_OperationComplete(CONN_FSM_EVENT cfeEvent)
{
HRESULT hrResult = S_OK;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_OPERATIONCOMPLETE == m_cfsState);
TraceCall("CIMAPSync::_ConnFSM_OperationComplete");
switch (cfeEvent)
{
case CFSM_EVENT_INITIALIZE:
// Clean up and send OnComplete callback to caller
hrResult = _OnOperationComplete();
// Proceed back to the IDLE state
hrResult = _ConnFSM_ChangeState(CFSM_STATE_IDLE);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
break;
case CFSM_EVENT_ERROR:
// Ignore errors, we're on the way back to IDLE
break;
default:
TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_OperationComplete, got cfeEvent = %lu", cfeEvent));
hrResult = TraceResult(E_INVALIDARG);
break;
} // switch
exit:
return hrResult;
} // _ConnFSM_OperationComplete
HRESULT CIMAPSync::_ConnFSM_ChangeState(CONN_FSM_STATE cfsNewState)
{
HRESULT hrResult;
IxpAssert(m_cRef > 0);
IxpAssert(cfsNewState < CFSM_STATE_MAX);
TraceCall("CIMAPSync::_ConnFSM_ChangeState");
if (CFSM_STATE_OPERATIONCOMPLETE == cfsNewState)
m_fTerminating = TRUE;
m_cfsPrevState = m_cfsState;
m_cfsState = cfsNewState;
hrResult = _ConnFSM_QueueEvent(CFSM_EVENT_INITIALIZE);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
exit:
return hrResult;
} // _ConnFSM_ChangeState
HRESULT CIMAPSync::_ConnFSM_QueueEvent(CONN_FSM_EVENT cfeEvent)
{
BOOL fResult;
HRESULT hrResult = S_OK;
IxpAssert(m_cRef > 0);
IxpAssert(cfeEvent < CFSM_EVENT_MAX);
TraceCall("CIMAPSync::_ConnFSM_QueueEvent");
fResult = PostMessage(m_hwndConnFSM, WM_CFSM_EVENT, cfeEvent, 0);
if (0 == fResult)
{
hrResult = TraceResult(E_FAIL);
goto exit;
}
exit:
return hrResult;
} // _ConnFSM_QueueEvent
HRESULT CIMAPSync::_LaunchOperation(void)
{
HRESULT hrResult = E_FAIL;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_STARTOPERATION == m_cfsState);
TraceCall("CIMAPSync::_LaunchOperation");
switch (m_sotCurrent)
{
case SOT_SYNC_FOLDER:
IxpAssert(OLE_E_BLANK == m_hrOperationResult);
hrResult = STORE_S_NOOP; // Nothing to do! We're already done!
m_hrOperationResult = S_OK; // If we got this far we must be successful
goto exit;
default:
// Do nothing for now
break;
} // switch
// Launch Operation (for now, this just means to pump send queue)
do
{
hrResult = _SendNextOperation(NOFLAGS);
TraceError(hrResult);
} while (S_OK == hrResult);
exit:
return hrResult;
} // _LaunchOperation
HRESULT CIMAPSync::_OnOperationComplete(void)
{
STOREERROR seErrorInfo;
STOREERROR *pErrorInfo = NULL;
HRESULT hrTemp;
HRESULT hrOperationResult;
IStoreCallback *pCallback;
STOREOPERATIONTYPE sotCurrent;
IxpAssert(m_cRef > 0);
IxpAssert(CFSM_STATE_OPERATIONCOMPLETE == m_cfsState);
// In some cases, CIMAPSync::Close does the OnComplete call for us
if (SOT_INVALID == m_sotCurrent)
{
IxpAssert(NULL == m_pCurrentCB);
IxpAssert(OLE_E_BLANK == m_hrOperationResult);
return S_OK;
}
IxpAssert(OLE_E_BLANK != m_hrOperationResult);
TraceCall("CIMAPSync::_OnOperationComplete");
if (NULL != m_pCurrentCB && FAILED(m_hrOperationResult))
{
FillStoreError(&seErrorInfo, m_hrOperationResult, 0, NULL, NULL);
pErrorInfo = &seErrorInfo;
}
// Ancient relic of the past: will be deleted when queue is removed
FlushOperationQueue(issNotConnected, E_FAIL);
// Remember a couple of things
pCallback = m_pCurrentCB;
sotCurrent = m_sotCurrent;
hrOperationResult = m_hrOperationResult;
// Reset all operation variables in case of re-entry during OnComplete call
m_pCurrentCB = NULL;
m_sotCurrent = SOT_INVALID;
m_hrOperationResult = OLE_E_BLANK;
m_fTerminating = FALSE;
m_idCurrent = FOLDERID_INVALID;
m_fSubscribe = FALSE;
SafeRelease(m_pCurrentHash);
SafeRelease(m_pListHash);
// Now we're ready to call OnComplete
if (NULL != pCallback)
{
// This should be the ONLY call to IStoreCallback::OnComplete in this class!
hrTemp = pCallback->OnComplete(sotCurrent, hrOperationResult, NULL, pErrorInfo);
TraceError(hrTemp);
// *** WARNING: At this point, we may be re-entered if OnComplete call puts up
// window. Make no references to module vars!
pCallback->Release();
}
return S_OK;
} // _OnOperationComplete
IMAP_SERVERSTATE CIMAPSync::_StoreOpToMinISS(STOREOPERATIONTYPE sot)
{
IMAP_SERVERSTATE issResult = issSelected;
switch (sot)
{
case SOT_INVALID:
IxpAssert(FALSE);
break;
case SOT_CONNECTION_STATUS:
case SOT_PUT_MESSAGE:
case SOT_SYNCING_STORE:
case SOT_CREATE_FOLDER:
case SOT_MOVE_FOLDER:
case SOT_DELETE_FOLDER:
case SOT_RENAME_FOLDER:
case SOT_SUBSCRIBE_FOLDER:
case SOT_UPDATE_FOLDER:
case SOT_SYNCING_DESCRIPTIONS:
issResult = issAuthenticated;
break;
case SOT_SYNC_FOLDER:
case SOT_GET_MESSAGE:
case SOT_COPYMOVE_MESSAGE:
case SOT_SEARCHING:
case SOT_DELETING_MESSAGES:
case SOT_SET_MESSAGEFLAGS:
case SOT_PURGING_MESSAGES:
issResult = issSelected;
break;
default:
IxpAssert(FALSE);
break;
} // switch
return issResult;
} // _StoreOpToMinISS
LRESULT CALLBACK CIMAPSync::_ConnFSMWndProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
CIMAPSync *pThis;
HRESULT hrTemp;
pThis = (CIMAPSync *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (uMsg)
{
case WM_CREATE:
IxpAssert(NULL == pThis);
pThis = (CIMAPSync *)((CREATESTRUCT *)lParam)->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM)pThis);
lResult = 0;
break;
case WM_DESTROY:
SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL);
break;
case WM_CFSM_EVENT:
IxpAssert(wParam < CFSM_EVENT_MAX);
IxpAssert(0 == lParam);
IxpAssert(IsWindow(hwnd));
hrTemp = pThis->_ConnFSM_HandleEvent((CONN_FSM_EVENT)wParam);
if (FAILED(hrTemp))
{
TraceResult(hrTemp);
pThis->m_hrOperationResult = hrTemp;
}
break;
default:
lResult = DefWindowProc(hwnd, uMsg, wParam, lParam);
break;
}
return lResult;
}
HRESULT CIMAPSync::_EnsureInited()
{
if (!m_fInited)
return CO_E_NOTINITIALIZED;
if (!m_pTransport)
return E_UNEXPECTED;
if (m_sotCurrent != SOT_INVALID)
{
AssertSz(m_sotCurrent != SOT_INVALID, "IMAPSync was called into during a command-execution. Bug in server queue?");
return E_UNEXPECTED;
}
return S_OK;
}
/*
* Function : EnsureSelected()
*
* Purpose: make sure we are in the selected folder state
* if we are selected, then we're done.
*
*
*/
HRESULT CIMAPSync::_EnsureSelected(void)
{
HRESULT hr;
LPSTR pszDestFldrPath = NULL;
TraceCall("CIMAPSync::_EnsureSelected");
AssertSingleThreaded;
IxpAssert(m_pStore);
IxpAssert(m_idIMAPServer != FOLDERID_INVALID);
// If current folder is already selected, no need to issue SELECT
if (FOLDERID_INVALID != m_idSelectedFolder &&
m_idSelectedFolder == m_idFolder)
{
hr = STORE_S_NOOP; // Success, but no SELECT command issued
goto exit;
}
if (m_idFolder == FOLDERID_INVALID)
{
// noone has called SetFolder on us yet, let's bail
// with a badfolder error
hr = TraceResult(STORE_E_BADFOLDERNAME);
goto exit;
}
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, m_idFolder, &pszDestFldrPath,
NULL, NULL, m_pStore, NULL, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// We're about to issue a SELECT command, so clear operation queue
// (it's filled with commands for previous folder)
OnFolderExit();
// Find out what translation mode we should be in
hr = SetTranslationMode(m_idFolder);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = m_pTransport->Select(tidSELECTION, (LPARAM) m_idFolder, this, pszDestFldrPath);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
SafeMemFree(pszDestFldrPath);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::SetIdleCallback(IStoreCallback *pDefaultCallback)
{
TraceCall("CIMAPSync::SetOwner");
AssertSingleThreaded;
ReplaceInterface(m_pDefCallback, pDefaultCallback);
return S_OK;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::SetConnectionState(CONNECT_STATE csNewState)
{
HRESULT hr;
TraceCall("CIMAPSync::SetConnectionState");
AssertSingleThreaded;
m_csNewConnState = csNewState;
if (CONNECT_STATE_CONNECT == csNewState)
{
hr = _Connect();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
m_fCreateSpecial = TRUE;
}
else if (CONNECT_STATE_DISCONNECT == csNewState)
{
hr = _Disconnect();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
else
{
AssertSz(FALSE, "What do you want?");
hr = TraceResult(E_INVALIDARG);
goto exit;
}
exit:
return hr;
}
//***************************************************************************
// Function: SynchronizeFolder
//
// Purpose:
// This function is used to tell CIMAPSync what parts of the message
// list to synchronize with the IMAP server, and any special sync options.
// The call is treated as a STANDING ORDER, meaning that if this function
// is called to get new headers, CIMAPSync assumes that the caller is always
// interested in new headers. Therefore, the next time the IMAP server informs
// us of new headers, we download them.
//***************************************************************************
HRESULT CIMAPSync::SynchronizeFolder(DWORD dwFlags, DWORD cHeaders, IStoreCallback *pCallback)
{
HRESULT hr;
TraceCall("CIMAPSync::SynchronizeFolder");
AssertSingleThreaded;
AssertSz(ISFLAGCLEAR(dwFlags, SYNC_FOLDER_CACHED_HEADERS) ||
ISFLAGSET(dwFlags, SYNC_FOLDER_NEW_HEADERS),
"Cannot currently sync cached headers without getting new headers as well");
IxpAssert(0 == (dwFlags & ~(SYNC_FOLDER_ALLFLAGS)));
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Special-case SYNC_FOLDER_PURGE_DELETED. It doesn't really belong here
// but it allows us to avoid adding a new function to IMessageServer.
// Do not allow its presence to affect our standing orders
if (SYNC_FOLDER_PURGE_DELETED & dwFlags)
{
// Need to set m_dwSyncFolderFlags with this flag because m_dwSyncToDo gets erased
Assert(0 == (dwFlags & ~(SYNC_FOLDER_PURGE_DELETED)));
dwFlags = m_dwSyncFolderFlags | SYNC_FOLDER_PURGE_DELETED;
}
m_dwSyncFolderFlags = dwFlags;
m_dwSyncToDo = dwFlags | SYNC_FOLDER_NOOP;
m_dwHighestCachedUID = 0;
exit:
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_SYNC_FOLDER, pCallback);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::GetMessage(MESSAGEID idMessage, IStoreCallback *pCallback)
{
HRESULT hr;
BOOL fNeedMsgSeqNum = FALSE;
TraceCall("CIMAPSync::GetMessage");
AssertSingleThreaded;
IxpAssert(MESSAGEID_INVALID != idMessage);
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
SafeRelease(m_pstmBody);
hr = CreatePersistentWriteStream(m_pFolder, &m_pstmBody, &m_faStream);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
m_idMessage = idMessage;
m_fGotBody = FALSE;
// To FETCH a message we need to translate MsgSeqNum to UID, so check if we can do it
if (FALSE == ISFLAGSET(m_dwSyncFolderFlags, (SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS)))
{
DWORD dwMsgSeqNum;
HRESULT hrTemp;
// Both SYNC_FOLDER_NEW_HEADERS and SYNC_FOLDER_CACHED_HEADERS have to be
// set to guarantee general MsgSeqNumToUID translation. Looks like we may
// have to get the translation ourselves, but check if we already have it
hrTemp = ImapUtil_UIDToMsgSeqNum(m_pTransport, (DWORD_PTR)idMessage, &dwMsgSeqNum);
if (FAILED(hrTemp))
fNeedMsgSeqNum = TRUE;
}
if (fNeedMsgSeqNum)
{
char szFetchArgs[50];
wnsprintfA(szFetchArgs, ARRAYSIZE(szFetchArgs), "%lu (UID)", idMessage);
hr = _EnqueueOperation(tidBODYMSN, (LPARAM) idMessage, icFETCH_COMMAND,
szFetchArgs, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
else
{
hr = _EnqueueOperation(tidBODY, (LPARAM) idMessage, icFETCH_COMMAND,
NULL, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
exit:
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_GET_MESSAGE, pCallback);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::PutMessage(FOLDERID idFolder,
MESSAGEFLAGS dwFlags,
LPFILETIME pftReceived,
IStream *pStream,
IStoreCallback *pCallback)
{
HRESULT hr;
IMAP_MSGFLAGS imfIMAPMsgFlags;
LPSTR pszDestFldrPath = NULL;
APPEND_SEND_INFO *pAppendInfo = NULL;
FOLDERINFO fiFolderInfo;
BOOL fSuppressRelease = FALSE;
TraceCall("CIMAPSync::PutMessage");
AssertSingleThreaded;
IxpAssert(FOLDERID_INVALID != m_idIMAPServer);
IxpAssert(NULL != pStream);
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Find out what translation mode we should be in
hr= SetTranslationMode(idFolder);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Create a APPEND_SEND_INFO structure
pAppendInfo = new APPEND_SEND_INFO;
if (NULL == pAppendInfo)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
ZeroMemory(pAppendInfo, sizeof(APPEND_SEND_INFO));
// Fill in the fields
ImapUtil_LoadRootFldrPrefix(m_pszAccountID, m_szRootFolderPrefix, ARRAYSIZE(m_szRootFolderPrefix));
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszDestFldrPath, NULL,
NULL, m_pStore, NULL, m_szRootFolderPrefix);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Convert flags to a string
imfIMAPMsgFlags = DwConvertARFtoIMAP(dwFlags);
hr = ImapUtil_MsgFlagsToString(imfIMAPMsgFlags, &pAppendInfo->pszMsgFlags, NULL);
if (FAILED(hr))
{
// The show must go on! Default to no IMAP msg flags
TraceResult(hr);
pAppendInfo->pszMsgFlags = NULL;
hr = S_OK; // Suppress error
}
// Get a date/time for INTERNALDATE attribute of this msg
if (NULL == pftReceived)
{
SYSTEMTIME stCurrentTime;
// Substitute current date/time
GetSystemTime(&stCurrentTime);
SystemTimeToFileTime(&stCurrentTime, &pAppendInfo->ftReceived);
}
else
pAppendInfo->ftReceived = *pftReceived;
pAppendInfo->lpstmMsg = pStream;
pStream->AddRef();
// Check for the case where destination is a special folder whose creation was deferred
hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
if (SUCCEEDED(hr))
{
if (FOLDER_CREATEONDEMAND & fiFolderInfo.dwFlags)
{
CREATE_FOLDER_INFO *pcfi;
Assert(FOLDER_NOTSPECIAL != fiFolderInfo.tySpecial);
pcfi = new CREATE_FOLDER_INFO;
if (NULL == pcfi)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
// Fill in all the fields
pcfi->pszFullFolderPath = PszDupA(pszDestFldrPath);
if (NULL == pcfi->pszFullFolderPath)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
pcfi->idFolder = FOLDERID_INVALID;
pcfi->dwFlags = 0;
pcfi->csfCurrentStage = CSF_INIT;
pcfi->dwCurrentSfType = fiFolderInfo.tySpecial;
pcfi->dwFinalSfType = fiFolderInfo.tySpecial;
pcfi->lParam = (LPARAM) pAppendInfo;
pcfi->pcoNextOp = PCO_APPENDMSG;
hr = CreateNextSpecialFolder(pcfi, NULL);
TraceError(hr); // CreateNextSpecialFolder deletes pcfi on its own if it fails
fSuppressRelease = TRUE; // It also frees pAppendInfo if it fails
m_pStore->FreeRecord(&fiFolderInfo);
goto exit; // Don't send APPEND command until after entire CREATE operation
}
m_pStore->FreeRecord(&fiFolderInfo);
}
// We're ready to send the APPEND command!
hr = _EnqueueOperation(tidUPLOADMSG, (LPARAM) pAppendInfo, icAPPEND_COMMAND,
pszDestFldrPath, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
if (NULL != pszDestFldrPath)
MemFree(pszDestFldrPath);
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_PUT_MESSAGE, pCallback);
else if (NULL != pAppendInfo && FALSE == fSuppressRelease)
{
SafeMemFree(pAppendInfo->pszMsgFlags);
delete pAppendInfo;
}
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::SetMessageFlags(LPMESSAGEIDLIST pList,
LPADJUSTFLAGS pFlags,
SETMESSAGEFLAGSFLAGS dwFlags,
IStoreCallback *pCallback)
{
HRESULT hr;
TraceCall("CIMAPSync::SetMessageFlags");
AssertSingleThreaded;
IxpAssert(m_cRef > 0);
IxpAssert(NULL == pList || pList->cMsgs > 0);
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = _SetMessageFlags(SOT_SET_MESSAGEFLAGS, pList, pFlags, pCallback);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_SET_MESSAGEFLAGS, pCallback);
return hr;
}
HRESULT CIMAPSync::GetServerMessageFlags(MESSAGEFLAGS *pFlags)
{
*pFlags = DwConvertIMAPtoARF(IMAP_MSG_ALLFLAGS);
return S_OK;
}
//***************************************************************************
// Helper function to mark messages
//***************************************************************************
HRESULT CIMAPSync::_SetMessageFlags(STOREOPERATIONTYPE sotOpType,
LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags,
IStoreCallback *pCallback)
{
HRESULT hr;
MARK_MSGS_INFO *pMARK_MSGS_INFO = NULL;
char szFlagArgs[200];
LPSTR pszFlagList;
DWORD dwLen;
LPSTR p;
IMAP_MSGFLAGS imfFlags;
DWORD dw;
ULONG ul;
TraceCall("CIMAPSync::_SetMessageFlags");
AssertSingleThreaded;
IxpAssert(NULL == pList || pList->cMsgs > 0);
// Construct a mark msg operation
// Check the requested flag adjustments
if (0 == pFlags->dwRemove && 0 == pFlags->dwAdd)
{
// Nothing to do here, exit with a smile
hr = S_OK;
goto exit;
}
if ((0 != pFlags->dwRemove && 0 != pFlags->dwAdd) ||
(0 != (pFlags->dwRemove & pFlags->dwAdd)))
{
// IMAP can't do any of the following:
// 1) add and removal of flags at the same time (NYI: takes 2 STORE cmds)
// 2) add/removal of same flag (makes no sense)
hr = TraceResult(E_INVALIDARG);
goto exit;
}
// If ARF_ENDANGERED is set, be sure to set ARF_READ so we don't mess up
// unread counts as returned by STATUS command
if (pFlags->dwAdd & ARF_ENDANGERED)
pFlags->dwAdd |= ARF_READ;
// Construct MARK_MSGS_INFO structure
pMARK_MSGS_INFO = new MARK_MSGS_INFO;
if (NULL == pMARK_MSGS_INFO)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
ZeroMemory(pMARK_MSGS_INFO, sizeof(MARK_MSGS_INFO));
// Create a rangelist
hr = CreateRangeList(&pMARK_MSGS_INFO->pMsgRange);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Remember these args so we can set the message flags after server confirmation
pMARK_MSGS_INFO->afFlags = *pFlags;
hr = CloneMessageIDList(pList, &pMARK_MSGS_INFO->pList);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
pMARK_MSGS_INFO->sotOpType = sotOpType;
// Get arguments for the STORE command
if (0 != pFlags->dwRemove)
szFlagArgs[0] = '-';
else
szFlagArgs[0] = '+';
p = szFlagArgs + 1;
p += wnsprintf(p, (ARRAYSIZE(szFlagArgs) - 1), "FLAGS.SILENT ");
imfFlags = DwConvertARFtoIMAP(pFlags->dwRemove ? pFlags->dwRemove : pFlags->dwAdd);
hr = ImapUtil_MsgFlagsToString(imfFlags, &pszFlagList, &dwLen);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
if (hr != S_FALSE)
{
IxpAssert(dwLen < (sizeof(szFlagArgs) - (p - szFlagArgs)));
StrCpyN(p, pszFlagList, ARRAYSIZE(szFlagArgs) - (int) (p - szFlagArgs));
MemFree(pszFlagList);
}
// Convert IDList to rangelist to submit to IIMAPTransport
if (NULL != pList)
{
for (dw = 0; dw < pList->cMsgs; dw++)
{
HRESULT hrTemp;
hrTemp = pMARK_MSGS_INFO->pMsgRange->AddSingleValue(PtrToUlong(pList->prgidMsg[dw]));
TraceError(hrTemp);
}
}
else
{
HRESULT hrTemp;
// pList == NULL means to tackle ALL messages
hrTemp = pMARK_MSGS_INFO->pMsgRange->AddRange(1, RL_LAST_MESSAGE);
TraceError(hrTemp);
}
IxpAssert(SUCCEEDED(pMARK_MSGS_INFO->pMsgRange->Cardinality(&ul)) && ul > 0);
// Send the command! (At last!)
hr = _EnqueueOperation(tidMARKMSGS, (LPARAM) pMARK_MSGS_INFO, icSTORE_COMMAND,
szFlagArgs, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::CopyMessages(IMessageFolder *pDestFldr,
COPYMESSAGEFLAGS dwOptions,
LPMESSAGEIDLIST pList,
LPADJUSTFLAGS pFlags,
IStoreCallback *pCallback)
{
HRESULT hr;
FOLDERID idDestFldr;
FOLDERINFO fiFolderInfo;
BOOL fFreeFldrInfo = FALSE;
CHAR szAccountId[CCHMAX_ACCOUNT_NAME];
TraceCall("CIMAPSync::CopyMoveMessages");
AssertSingleThreaded;
IxpAssert(FOLDERID_INVALID != m_idIMAPServer);
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Check if we can do an IMAP COPY command to satisfy this copy request
hr = pDestFldr->GetFolderId(&idDestFldr);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = m_pStore->GetFolderInfo(idDestFldr, &fiFolderInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
fFreeFldrInfo = TRUE;
GetFolderAccountId(&fiFolderInfo, szAccountId, ARRAYSIZE(szAccountId));
if (0 == lstrcmpi(m_pszAccountID, szAccountId) && FOLDER_IMAP == fiFolderInfo.tyFolder)
{
IMAP_COPYMOVE_INFO *pCopyInfo;
LPSTR pszDestFldrPath;
DWORD dw;
ULONG ul;
// This copy can be accomplished with an IMAP copy command!
// Check args
if (NULL != pFlags && (0 != pFlags->dwAdd || 0 != pFlags->dwRemove))
// IMAP cannot set the flags of copied msg. We would either have to set
// flags on source before copying, or go to destination folder and set flags
TraceResult(E_INVALIDARG); // Record error but continue (error not fatal)
// Find out what translation mode we should be in
hr = SetTranslationMode(idDestFldr);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Construct CopyMoveInfo structure
pCopyInfo = new IMAP_COPYMOVE_INFO;
if (NULL == pCopyInfo)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
pCopyInfo->dwOptions = dwOptions;
pCopyInfo->idDestFldr = idDestFldr;
hr = CloneMessageIDList(pList, &pCopyInfo->pList);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = CreateRangeList(&pCopyInfo->pCopyRange);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Convert IDList to rangelist to submit to IIMAPTransport
if (NULL != pList)
{
for (dw = 0; dw < pList->cMsgs; dw++)
{
HRESULT hrTemp;
hrTemp = pCopyInfo->pCopyRange->AddSingleValue(PtrToUlong(pList->prgidMsg[dw]));
TraceError(hrTemp);
}
}
else
{
HRESULT hrTemp;
// pList == NULL means to tackle ALL messages
hrTemp = pCopyInfo->pCopyRange->AddRange(1, RL_LAST_MESSAGE);
TraceError(hrTemp);
}
IxpAssert(SUCCEEDED(pCopyInfo->pCopyRange->Cardinality(&ul)) && ul > 0);
// Construct destination folder path
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idDestFldr, &pszDestFldrPath,
NULL, NULL, m_pStore, NULL, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Send command to server
hr = _EnqueueOperation(tidCOPYMSGS, (LPARAM) pCopyInfo, icCOPY_COMMAND,
pszDestFldrPath, uiNORMAL_PRIORITY);
MemFree(pszDestFldrPath);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
else
{
// This is a standard (download from src-save to dest) copy: let caller do it
hr = STORE_E_NOSERVERCOPY;
goto exit; // Don't record this error value, it's expected
}
exit:
if (fFreeFldrInfo)
m_pStore->FreeRecord(&fiFolderInfo);
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_COPYMOVE_MESSAGE, pCallback);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::DeleteMessages(DELETEMESSAGEFLAGS dwOptions,
LPMESSAGEIDLIST pList,
IStoreCallback *pCallback)
{
ADJUSTFLAGS afFlags;
HRESULT hr;
TraceCall("CIMAPSync::DeleteMessages");
AssertSingleThreaded;
IxpAssert(NULL == pList || pList->cMsgs > 0);
// This function currently only supports IMAP deletion model. Trashcan NYI.
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
if (dwOptions & DELETE_MESSAGE_UNDELETE)
{
afFlags.dwAdd = 0;
afFlags.dwRemove = ARF_ENDANGERED;
}
else
{
afFlags.dwAdd = ARF_ENDANGERED;
afFlags.dwRemove = 0;
}
hr = _SetMessageFlags(SOT_DELETING_MESSAGES, pList, &afFlags, pCallback);
if (FAILED(hr))
{
TraceError(hr);
goto exit;
}
exit:
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_DELETING_MESSAGES, pCallback);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::SynchronizeStore(FOLDERID idParent,
DWORD dwFlags,
IStoreCallback *pCallback)
{
HRESULT hr = S_OK;
TraceCall("CIMAPSync::SynchronizeStore");
AssertSingleThreaded;
IxpAssert(SOT_INVALID == m_sotCurrent);
IxpAssert(NULL == m_pCurrentCB);
// This function currently ignores the dwFlags argument
m_cFolders = 0;
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Force mailbox translation since we only issue LIST *
hr = SetTranslationMode(FOLDERID_INVALID);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
SafeRelease(m_pCurrentHash);
SafeRelease(m_pListHash);
m_sotCurrent = SOT_SYNCING_STORE;
m_pCurrentCB = pCallback;
if (NULL != pCallback)
pCallback->AddRef();
hr = CreateFolderHash(m_pStore, m_idIMAPServer, &m_pCurrentHash);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = MimeOleCreateHashTable(CHASH_BUCKETS, TRUE, &m_pListHash);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
if (INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar)
{
// Set us up to find out root hierarchy char
m_phcfHierarchyCharInfo = new HIERARCHY_CHAR_FINDER;
if (NULL == m_phcfHierarchyCharInfo)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
ZeroMemory(m_phcfHierarchyCharInfo, sizeof(HIERARCHY_CHAR_FINDER));
}
ImapUtil_LoadRootFldrPrefix(m_pszAccountID, m_szRootFolderPrefix, ARRAYSIZE(m_szRootFolderPrefix));
hr = _StartFolderList((LPARAM)FOLDERID_INVALID);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
if (FAILED(hr))
{
m_sotCurrent = SOT_INVALID;
m_pCurrentCB = NULL;
m_fTerminating = FALSE;
if (NULL != pCallback)
pCallback->Release();
}
else
hr = _BeginOperation(m_sotCurrent, m_pCurrentCB);
return hr;
} // SynchronizeStore
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::CreateFolder(FOLDERID idParent,
SPECIALFOLDER tySpecial,
LPCSTR pszName,
FLDRFLAGS dwFlags,
IStoreCallback *pCallback)
{
HRESULT hr;
CHAR chHierarchy;
LPSTR pszFullPath=NULL;
CREATE_FOLDER_INFO *pcfi=NULL;
DWORD dwFullPathLen;
LPSTR pszEnd;
TraceCall("CIMAPSync::CreateFolder");
AssertSingleThreaded;
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Validate folder name
hr = CheckFolderNameValidity(pszName);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Find out what translation mode we should be in
hr = SetTranslationMode(idParent);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
else if (S_FALSE == hr)
{
// Parent not translatable from UTF7. In such a case, we only allow creation
// if child foldername is composed ENTIRELY of USASCII
if (FALSE == isUSASCIIOnly(pszName))
{
// We can't create this folder: parent prohibits UTF7 translation
hr = TraceResult(STORE_E_NOTRANSLATION);
goto exit;
}
}
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idParent, &pszFullPath, &dwFullPathLen,
&chHierarchy, m_pStore, pszName, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
pcfi = new CREATE_FOLDER_INFO;
if (NULL == pcfi)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
// Fill in all the fields
pcfi->pszFullFolderPath = pszFullPath;
pcfi->idFolder = FOLDERID_INVALID;
pcfi->dwFlags = 0;
pcfi->csfCurrentStage = CSF_INIT;
pcfi->dwCurrentSfType = FOLDER_NOTSPECIAL;
pcfi->dwFinalSfType = FOLDER_NOTSPECIAL;
pcfi->lParam = NULL;
pcfi->pcoNextOp = PCO_NONE;
// Send the CREATE command
hr = _EnqueueOperation(tidCREATE, (LPARAM)pcfi, icCREATE_COMMAND, pszFullPath, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// If there is a trailing HC (required to create folder-bearing folders on UW IMAP),
// remove it from pszFullPath so that LIST and SUBSCRIBE do not carry it (IE5 bug #60054)
pszEnd = CharPrev(pszFullPath, pszFullPath + dwFullPathLen);
if (chHierarchy == *pszEnd)
{
*pszEnd = '\0';
Assert(*CharPrev(pszFullPath, pszEnd) != chHierarchy); // Shouldn't get > 1 HC at end
}
exit:
if (FAILED(hr))
{
SafeMemFree(pszFullPath);
delete pcfi;
}
else
hr = _BeginOperation(SOT_CREATE_FOLDER, pCallback);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::MoveFolder(FOLDERID idFolder,
FOLDERID idParentNew,
IStoreCallback *pCallback)
{
HRESULT hr;
TraceCall("CIMAPSync::MoveFolder");
AssertSingleThreaded;
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = TraceResult(E_NOTIMPL);
exit:
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_MOVE_FOLDER, pCallback);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::RenameFolder(FOLDERID idFolder,
LPCSTR pszName,
IStoreCallback *pCallback)
{
HRESULT hr;
FOLDERINFO fiFolderInfo;
LPSTR pszOldPath = NULL;
LPSTR pszNewPath = NULL;
char chHierarchy;
BOOL fFreeInfo = FALSE;
TraceCall("CIMAPSync::RenameFolder");
AssertSingleThreaded;
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Validate folder name
hr = CheckFolderNameValidity(pszName);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Find out what translation mode we should be in
hr = SetTranslationMode(idFolder);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
else if (S_FALSE == hr)
{
// Folder not translatable from UTF7. In such a case, we only allow creation
// if new foldername is composed ENTIRELY of USASCII. A bit conservative, yes
// (if leaf node is only un-translatable part, we COULD rename), but I'm too
// lazy to check for FOLDER_NOTRANSLATEUTF7 all the way to server node.
if (FALSE == isUSASCIIOnly(pszName))
{
// We can't rename this folder: we assume parents prohibit UTF7 translation
hr = TraceResult(STORE_E_NOTRANSLATION);
goto exit;
}
}
hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Check validity
fFreeInfo = TRUE;
IxpAssert(FOLDER_NOTSPECIAL == fiFolderInfo.tySpecial);
IxpAssert('\0' != fiFolderInfo.pszName);
IxpAssert('\0' != pszName);
if (0 == lstrcmp(fiFolderInfo.pszName, pszName))
{
hr = E_INVALIDARG; // Nothing to do! Return error.
goto exit;
}
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszOldPath, NULL,
&chHierarchy, m_pStore, NULL, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, fiFolderInfo.idParent, &pszNewPath,
NULL, &chHierarchy, m_pStore, pszName, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = RenameFolderHelper(idFolder, pszOldPath, chHierarchy, pszNewPath);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_RENAME_FOLDER, pCallback);
SafeMemFree(pszOldPath);
SafeMemFree(pszNewPath);
if (fFreeInfo)
m_pStore->FreeRecord(&fiFolderInfo);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::DeleteFolder(FOLDERID idFolder,
DELETEFOLDERFLAGS dwFlags,
IStoreCallback *pCallback)
{
HRESULT hr;
DELETE_FOLDER_INFO *pdfi = NULL;
LPSTR pszPath = NULL;
CHAR chHierarchy;
TraceCall("CIMAPSync::DeleteFolder");
AssertSingleThreaded;
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Find out what translation mode we should be in
hr = SetTranslationMode(idFolder);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszPath, NULL,
&chHierarchy, m_pStore, NULL, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Create a CreateFolderInfo structure
if (!MemAlloc((LPVOID *)&pdfi, sizeof(DELETE_FOLDER_INFO)))
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
pdfi->pszFullFolderPath = pszPath;
pdfi->cHierarchyChar = chHierarchy;
pdfi->idFolder = idFolder;
// Send the DELETE command
hr = _EnqueueOperation(tidDELETEFLDR, (LPARAM)pdfi, icDELETE_COMMAND, pszPath, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
if (FAILED(hr))
{
SafeMemFree(pszPath);
SafeMemFree(pdfi);
}
else
hr = _BeginOperation(SOT_DELETE_FOLDER, pCallback);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::SubscribeToFolder(FOLDERID idFolder,
BOOL fSubscribe,
IStoreCallback *pCallback)
{
HRESULT hr;
LPSTR pszPath = NULL;
TraceCall("CIMAPSync::SubscribeToFolder");
AssertSingleThreaded;
IxpAssert(FOLDERID_INVALID == m_idCurrent);
IxpAssert(FALSE == m_fSubscribe);
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Find out what translation mode we should be in
hr = SetTranslationMode(idFolder);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszPath, NULL, NULL,
m_pStore, NULL, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Send the SUBSCRIBE/UNSUBSCRIBE command
m_idCurrent = idFolder;
m_fSubscribe = fSubscribe;
hr = _EnqueueOperation(tidSUBSCRIBE, 0, fSubscribe ? icSUBSCRIBE_COMMAND :
icUNSUBSCRIBE_COMMAND, pszPath, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
SafeMemFree(pszPath);
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_SUBSCRIBE_FOLDER, pCallback);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::GetFolderCounts(FOLDERID idFolder, IStoreCallback *pCallback)
{
HRESULT hr;
LPSTR pszPath = NULL;
DWORD dwCapabilities;
FOLDERINFO fiFolderInfo;
BOOL fFreeFldrInfo = FALSE;
TraceCall("CIMAPSync::GetFolderCounts");
AssertSingleThreaded;
IxpAssert(FOLDERID_INVALID != idFolder);
IxpAssert(NULL != pCallback);
hr = _EnsureInited();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Find out what translation mode we should be in
hr = SetTranslationMode(idFolder);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Perform some verification: folder cannot be \NoSelect, server must be IMAP4rev1
// Unfortunately, we can't get capability unless we're currently connected
hr = m_pTransport->IsState(IXP_IS_AUTHENTICATED);
if (S_OK == hr)
{
hr = m_pTransport->Capability(&dwCapabilities);
if (SUCCEEDED(hr) && 0 == (dwCapabilities & IMAP_CAPABILITY_IMAP4rev1))
{
// This server does not support STATUS command, we don't support alternate
// method of unread count update (eg, EXAMINE folder)
hr = E_NOTIMPL;
goto exit;
}
}
// If not connected then we'll check capabilities during connection
hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
if (SUCCEEDED(hr))
{
fFreeFldrInfo = TRUE;
if (fiFolderInfo.dwFlags & (FOLDER_NOSELECT | FOLDER_NONEXISTENT))
{
// This folder cannot have an unread count because it cannot contain messages
hr = TraceResult(STORE_E_NOSERVERSUPPORT);
goto exit;
}
}
hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszPath, NULL, NULL,
m_pStore, NULL, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
ImapUtil_LoadRootFldrPrefix(m_pszAccountID, m_szRootFolderPrefix, ARRAYSIZE(m_szRootFolderPrefix));
hr = LoadSaveRootHierarchyChar(fLOAD_HC);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Send the STATUS command
hr = _EnqueueOperation(tidSTATUS, (LPARAM)idFolder, icSTATUS_COMMAND, pszPath, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
SafeMemFree(pszPath);
if (fFreeFldrInfo)
m_pStore->FreeRecord(&fiFolderInfo);
if (SUCCEEDED(hr))
hr = _BeginOperation(SOT_UPDATE_FOLDER, pCallback);
return hr;
}
STDMETHODIMP CIMAPSync::GetNewGroups(LPSYSTEMTIME pSysTime, IStoreCallback *pCallback)
{
return E_NOTIMPL;
}
HRESULT STDMETHODCALLTYPE CIMAPSync::ExpungeOnExit(void)
{
HWND hwndParent;
HRESULT hrResult = S_OK;
// Check if user wants us to purge on exit (only if no operations in progress)
if (DwGetOption(OPT_IMAPPURGE))
{
hrResult = GetParentWindow(0, &hwndParent);
if (SUCCEEDED(hrResult))
{
hrResult = PurgeMessageProgress(hwndParent);
TraceError(hrResult);
}
}
return hrResult;
} // ExpungeOnExit
HRESULT CIMAPSync::Cancel(CANCELTYPE tyCancel)
{
// $TODO: Translate tyCancel into an HRESULT to return to the caller
FlushOperationQueue(issNotConnected, STORE_E_OPERATION_CANCELED);
_Disconnect();
// The m_hrOperationResult and m_szOperationDetails/m_szOperationProblem
// vars can be blown away by by _OnCmdComplete caused by disconnect
m_hrOperationResult = STORE_E_OPERATION_CANCELED;
// Verify that we are indeed terminating current operation: if not, FORCE IT!
if (FALSE == m_fTerminating)
{
HRESULT hrTemp;
IxpAssert(FALSE); // This should not happen: fix the problem
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_ERROR);
TraceError(hrTemp);
}
return S_OK;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::_LoadAccountInfo()
{
HRESULT hr;
FOLDERINFO fi;
FOLDERINFO *pfiFree=NULL;
IImnAccount *pAcct=NULL;
CHAR szAccountId[CCHMAX_ACCOUNT_NAME];
TraceCall("CIMAPSync::_LoadAccountInfo");
IxpAssert (m_idIMAPServer);
IxpAssert (m_pStore);
IxpAssert (g_pAcctMan);
if (!m_pStore || !g_pAcctMan)
{
hr = E_UNEXPECTED;
goto exit;
}
hr = m_pStore->GetFolderInfo(m_idIMAPServer, &fi);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
pfiFree = &fi;
hr = GetFolderAccountId(&fi, szAccountId, ARRAYSIZE(szAccountId));
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
m_pszAccountID = PszDupA(szAccountId);
if (!m_pszAccountID)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, szAccountId, &pAcct);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// failure of the account name is recoverable
pAcct->GetPropSz(AP_ACCOUNT_NAME, m_szAccountName, ARRAYSIZE(m_szAccountName));
exit:
if (pfiFree)
m_pStore->FreeRecord(pfiFree);
ReleaseObj(pAcct);
return hr;
}
HRESULT CIMAPSync::_LoadTransport()
{
HRESULT hr;
TCHAR szLogfilePath[MAX_PATH];
TCHAR *pszLogfilePath = NULL;
IImnAccount *pAcct=NULL;
TraceCall("CIMAPSync::_LoadTransport");
IxpAssert (g_pAcctMan);
IxpAssert (m_pszAccountID);
if (!g_pAcctMan)
{
hr = E_UNEXPECTED;
goto exit;
}
// Create and initialize IMAP transport
hr = CreateIMAPTransport2(&m_pTransport);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Check if logging is enabled
if (DwGetOption(OPT_MAIL_LOGIMAP4))
{
char szDirectory[MAX_PATH];
char szLogFileName[MAX_PATH];
DWORD cb;
*szDirectory = 0;
// Get the log filename
cb = GetOption(OPT_MAIL_IMAP4LOGFILE, szLogFileName, ARRAYSIZE(szLogFileName));
if (0 == cb)
{
// Bring out the defaults, and blast it back into registry
StrCpyN(szLogFileName, c_szDefaultImap4Log, ARRAYSIZE(szLogFileName));
SetOption(OPT_MAIL_IMAP4LOGFILE, (void *)c_szDefaultImap4Log,
lstrlen(c_szDefaultImap4Log) + sizeof(TCHAR), NULL, 0);
}
m_pStore->GetDirectory(szDirectory, ARRAYSIZE(szDirectory));
pszLogfilePath = PathCombineA(szLogfilePath, szDirectory, szLogFileName);
}
hr = m_pTransport->InitNew(pszLogfilePath, this);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = m_pTransport->SetDefaultCP(IMAP_MBOXXLATE_DEFAULT | IMAP_MBOXXLATE_VERBATIMOK, GetACP());
TraceError(hr);
hr = m_pTransport->EnableFetchEx(IMAP_FETCHEX_ENABLE);
if (FAILED(hr))
{
// It would be easy for us to add code to handle irtUPDATE_MSG, but nothing is currently in place
TraceResult(hr);
goto exit;
}
hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_pszAccountID, &pAcct);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Fill m_rInetServerInfo
hr = m_pTransport->InetServerFromAccount(pAcct, &m_rInetServerInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
ReleaseObj(pAcct);
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::_Connect(void)
{
HRESULT hr;
IXPSTATUS ixpCurrentStatus;
INETSERVER rServerInfo;
BOOL fForceReconnect = FALSE;
IImnAccount *pAcct;
HRESULT hrTemp;
TraceCall("CIMAPSync::_Connect");
IxpAssert (g_pAcctMan);
IxpAssert (m_cRef > 0);
IxpAssert (m_pTransport);
if (!g_pAcctMan)
return E_UNEXPECTED;
// Check if any connection settings changed
hrTemp = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_pszAccountID, &pAcct);
TraceError(hrTemp);
if (SUCCEEDED(hrTemp))
{
hrTemp = m_pTransport->InetServerFromAccount(pAcct, &rServerInfo);
TraceError(hrTemp);
if (SUCCEEDED(hrTemp))
{
// Check if anything changed
if (m_rInetServerInfo.rasconntype != rServerInfo.rasconntype ||
m_rInetServerInfo.dwPort != rServerInfo.dwPort ||
m_rInetServerInfo.fSSL != rServerInfo.fSSL ||
m_rInetServerInfo.fTrySicily != rServerInfo.fTrySicily ||
m_rInetServerInfo.dwTimeout != rServerInfo.dwTimeout ||
0 != lstrcmp(m_rInetServerInfo.szUserName, rServerInfo.szUserName) ||
('\0' != rServerInfo.szPassword[0] &&
0 != lstrcmp(m_rInetServerInfo.szPassword, rServerInfo.szPassword)) ||
0 != lstrcmp(m_rInetServerInfo.szServerName, rServerInfo.szServerName) ||
0 != lstrcmp(m_rInetServerInfo.szConnectoid, rServerInfo.szConnectoid))
{
CopyMemory(&m_rInetServerInfo, &rServerInfo, sizeof(m_rInetServerInfo));
fForceReconnect = TRUE;
}
}
pAcct->Release();
}
// Find out if we're already connected or in the middle of connecting
hr = m_pTransport->GetStatus(&ixpCurrentStatus);
if (FAILED(hr))
{
// We'll call IIMAPTransport::Connect and see what happens
TraceResult(hr);
hr = S_OK; // Suppress error
ixpCurrentStatus = IXP_DISCONNECTED;
}
// If we're to force a reconnect and we're not currently disconnected, disconnect us
if (fForceReconnect && IXP_DISCONNECTED != ixpCurrentStatus)
{
m_fReconnect = TRUE; // Prohibit abortion of current operation due to disconnect
hrTemp = m_pTransport->DropConnection();
TraceError(hrTemp);
m_fReconnect = FALSE;
}
// Ask our client if we can connect. If no CB or if failure, we just try to connect
// Make sure we call CanConnect after DropConnection above, to avoid msg pumping
if (NULL != m_pCurrentCB)
{
hr = m_pCurrentCB->CanConnect(m_pszAccountID,
SOT_PURGING_MESSAGES == m_sotCurrent ? CC_FLAG_DONTPROMPT : NOFLAGS);
if (S_OK != hr)
{
// Make sure all non-S_OK success codes are treated as failures
// Convert all error codes to HR_E_USER_CANCEL_CONNECT if we were purging-on-exit
// This prevents error dialogs while purging-on-exit.
hr = TraceResult(hr);
if (SUCCEEDED(hr) || SOT_PURGING_MESSAGES == m_sotCurrent)
hr = HR_E_USER_CANCEL_CONNECT;
goto exit;
}
}
// If we're already in the middle of connecting, do nothing and return successful HRESULT
if (IXP_DISCONNECTED == ixpCurrentStatus || IXP_DISCONNECTING == ixpCurrentStatus ||
fForceReconnect)
{
// Make sure m_rInetServerInfo is loaded with latest cached password from user
// This allows reconnect without user intervention if user didn't save password
GetPassword(m_rInetServerInfo.dwPort, m_rInetServerInfo.szServerName,
m_rInetServerInfo.szUserName, m_rInetServerInfo.szPassword,
ARRAYSIZE(m_rInetServerInfo.szPassword));
// We're neither connected nor in the process of connecting: start connecting
hr = m_pTransport->Connect(&m_rInetServerInfo, iitAUTHENTICATE, iitDISABLE_ONCOMMAND);
}
else
{
// "Do Nothing" in the comment above now means to kick the FSM to the next state
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_CONNCOMPLETE);
TraceError(hrTemp);
}
exit:
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::_Disconnect(void)
{
HRESULT hr;
IXPSTATUS ixpCurrentStatus;
TraceCall("CIMAPSync::_Disconnect");
IxpAssert(m_cRef > 0);
// Find out if we're already disconnected or in the middle of disconnecting
hr = m_pTransport->GetStatus(&ixpCurrentStatus);
if (FAILED(hr))
{
// We'll call IIMAPTransport::DropConnection and see what happens
TraceResult(hr);
hr = S_OK; // Suppress error
ixpCurrentStatus = IXP_CONNECTED;
}
// If we're already in the middle of disconnecting, do nothing and return successful HRESULT
if (IXP_DISCONNECTED != ixpCurrentStatus &&
IXP_DISCONNECTING != ixpCurrentStatus && NULL != m_pTransport && FALSE == m_fDisconnecting)
{
m_fDisconnecting = TRUE;
m_hrOperationResult = STORE_E_OPERATION_CANCELED;
hr = m_pTransport->DropConnection();
}
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::_BeginOperation(STOREOPERATIONTYPE sotOpType,
IStoreCallback *pCallback)
{
HRESULT hr;
STOREOPERATIONINFO soi;
STOREOPERATIONINFO *psoi=NULL;
IxpAssert(SOT_INVALID != sotOpType);
m_sotCurrent = sotOpType;
ReplaceInterface(m_pCurrentCB, pCallback);
m_hrOperationResult = OLE_E_BLANK; // Unitialized state
m_szOperationProblem[0] = '\0';
m_szOperationDetails[0] = '\0';
m_fTerminating = FALSE;
// Kickstart the connection state machine
hr = _ConnFSM_QueueEvent(CFSM_EVENT_CMDAVAIL);
if (FAILED(hr))
{
TraceResult(hr);
}
else
{
if (sotOpType == SOT_GET_MESSAGE)
{
// provide message id on get message start
soi.cbSize = sizeof(STOREOPERATIONINFO);
soi.idMessage = m_idMessage;
psoi = &soi;
}
if (pCallback)
pCallback->OnBegin(sotOpType, psoi, this);
hr = E_PENDING;
}
return hr;
}
//***************************************************************************
// Function: _EnqueueOperation
//
// Purpose:
// This function enqueues IMAP operations for execution once we have
// entered the SELECTED state on the IMAP server.
//
// Arguments:
// WPARAM wParam [in] - transaction ID identifying this operation.
// This ID is always returned to CmdCompletionNotification, and possibly
// returned to any untagged responses which result from the given cmd.
// LPARAM lParam [in] - lParam associated with this transaction.
// IMAP_COMMAND icCommandID [in] - this identifies the IMAP command the
// caller wishes to send to the IMAP server.
// LPSTR pszCmdArgs [in] - the command arguments. Pass in NULL if the
// queued command has no arguments.
// UINT uiPriority [in] - a priority associated with this IMAP command.
// The value of "0" is highest priority. Before an IMAP command of
// a given priority can be sent, there must be NO higher-priority cmds
// waiting.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::_EnqueueOperation(WPARAM wParam, LPARAM lParam,
IMAP_COMMAND icCommandID, LPCSTR pszCmdArgs,
UINT uiPriority)
{
IMAP_OPERATION *pioCommand;
IMAP_OPERATION *pioPrev, *pioCurrent;
HRESULT hr = S_OK;
TraceCall("CIMAPSync::_EnqueueOperation");
IxpAssert(m_cRef > 0);
// Construct a IMAP_OPERATION queue element for this command
pioCommand = new IMAP_OPERATION;
if (NULL == pioCommand)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
pioCommand->wParam = wParam;
pioCommand->lParam = lParam;
pioCommand->icCommandID = icCommandID;
pioCommand->pszCmdArgs = PszDupA(pszCmdArgs);
pioCommand->uiPriority = uiPriority;
pioCommand->issMinimum = IMAPCmdToMinISS(icCommandID);
IxpAssert(pioCommand->issMinimum >= issNonAuthenticated);
// Refcount if this is a streamed operation
if (tidRENAME == wParam ||
tidRENAMESUBSCRIBE == wParam ||
tidRENAMELIST == wParam ||
tidRENAMERENAME == wParam ||
tidRENAMESUBSCRIBE_AGAIN == wParam ||
tidRENAMEUNSUBSCRIBE == wParam)
((CRenameFolderInfo *)lParam)->AddRef();
// Insert element into the queue
// Find a node which has lower priority than the cmd we want to enqueue
pioPrev = NULL;
pioCurrent = m_pioNextOperation;
while (NULL != pioCurrent && pioCurrent->uiPriority <= uiPriority)
{
// Advance both pointers
pioPrev = pioCurrent;
pioCurrent = pioCurrent->pioNextCommand;
}
// pioPrev now points to the insertion point
if (NULL == pioPrev)
{
// Insert command at head of queue
pioCommand->pioNextCommand = m_pioNextOperation;
m_pioNextOperation = pioCommand;
}
else {
// Insert command in middle/end of queue
pioCommand->pioNextCommand = pioCurrent;
pioPrev->pioNextCommand = pioCommand;
}
// Try to send immediately if we're in correct state
if (CFSM_STATE_WAITFOROPERATIONDONE == m_cfsState)
{
do {
hr = _SendNextOperation(snoDO_NOT_DISPOSE);
TraceError(hr);
} while (S_OK == hr);
}
exit:
return hr;
}
//***************************************************************************
// Function: _SendNextOperation
//
// Purpose:
// This function sends the next IMAP operation in the queue if the
// conditions are correct. Currently, these conditions are:
// a) We are in the SELECTED state on the IMAP server
// b) The IMAP operation queue is not empty
//
// Arguments:
// DWORD dwFlags [in] - one of the following:
// snoDO_NOT_DISPOSE - do not dispose of LPARAM if error occurs, typically
// because EnqueueOperation will return error to caller thus causing
// caller to dispose of data.
//
// Returns:
// S_OK if there are more operations available to be sent. S_FALSE if no more
// IMAP operations can be sent at this time. Failure result if an error occured.
//***************************************************************************
HRESULT CIMAPSync::_SendNextOperation(DWORD dwFlags)
{
IMAP_OPERATION *pioNextCmd;
IMAP_OPERATION *pioPrev;
HRESULT hr;
TraceCall("CIMAPSync::_SendNextOperation");
IxpAssert(m_cRef > 0);
// Check if conditions permit sending of an IMAP operation
hr = m_pTransport->IsState(IXP_IS_AUTHENTICATED);
if (S_OK != hr)
{
hr = S_FALSE; // No operations to send (YET)
goto exit;
}
// Look for next eligible command
hr = GetNextOperation(&pioNextCmd);
if (STORE_S_NOOP == hr || FAILED(hr))
{
TraceError(hr);
hr = S_FALSE;
goto exit;
}
// Send it
hr = S_OK;
switch (pioNextCmd->icCommandID)
{
case icFETCH_COMMAND:
{
LPSTR pszFetchArgs;
char szFetchArgs[CCHMAX_CMDLINE];
// Check if this is a BODY FETCH. We have to construct args for body fetch
if (tidBODY == pioNextCmd->wParam)
{
DWORD dwCapabilities;
// Check if this is IMAP4 or IMAP4rev1 (RFC822.PEEK or BODY.PEEK[])
IxpAssert(NULL == pioNextCmd->pszCmdArgs);
hr = m_pTransport->Capability(&dwCapabilities);
if (FAILED(hr))
{
TraceResult(hr);
dwCapabilities = IMAP_CAPABILITY_IMAP4; // Carry on assuming IMAP4
}
wnsprintf(szFetchArgs, ARRAYSIZE(szFetchArgs), "%lu (%s UID)", pioNextCmd->lParam,
(dwCapabilities & IMAP_CAPABILITY_IMAP4rev1) ? "BODY.PEEK[]" : "RFC822.PEEK");
pszFetchArgs = szFetchArgs;
}
else
{
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
pszFetchArgs = pioNextCmd->pszCmdArgs;
}
hr = m_pTransport->Fetch(pioNextCmd->wParam, pioNextCmd->lParam,
this, NULL, USE_UIDS, pszFetchArgs); // We always use UIDs
TraceError(hr);
}
break;
case icSTORE_COMMAND:
{
MARK_MSGS_INFO *pMARK_MSGS_INFO;
pMARK_MSGS_INFO = (MARK_MSGS_INFO *) pioNextCmd->lParam;
IxpAssert(tidMARKMSGS == pioNextCmd->wParam);
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
IxpAssert(NULL != pioNextCmd->lParam);
hr = m_pTransport->Store(pioNextCmd->wParam,
pioNextCmd->lParam, this, pMARK_MSGS_INFO->pMsgRange,
USE_UIDS, pioNextCmd->pszCmdArgs); // We always use UIDs
TraceError(hr);
}
break;
case icCOPY_COMMAND:
{
IMAP_COPYMOVE_INFO *pCopyMoveInfo;
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
IxpAssert(NULL != pioNextCmd->lParam);
pCopyMoveInfo = (IMAP_COPYMOVE_INFO *) pioNextCmd->lParam;
IxpAssert(NULL != pCopyMoveInfo->pCopyRange);
// Find out what translation mode we should be in
hr = SetTranslationMode(pCopyMoveInfo->idDestFldr);
if (FAILED(hr))
{
TraceResult(hr);
break;
}
hr = m_pTransport->Copy(pioNextCmd->wParam, pioNextCmd->lParam,
this, pCopyMoveInfo->pCopyRange,
USE_UIDS, pioNextCmd->pszCmdArgs); // We always use UIDs
TraceError(hr);
}
break; // icCOPY_COMMAND
case icCLOSE_COMMAND:
IxpAssert(NULL == pioNextCmd->pszCmdArgs);
hr = m_pTransport->Close(pioNextCmd->wParam, pioNextCmd->lParam, this);
TraceError(hr);
break;
case icAPPEND_COMMAND:
{
APPEND_SEND_INFO *pAppendInfo;
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
IxpAssert(NULL != pioNextCmd->lParam);
pAppendInfo = (APPEND_SEND_INFO *) pioNextCmd->lParam;
hr = m_pTransport->Append(pioNextCmd->wParam, pioNextCmd->lParam,
this, pioNextCmd->pszCmdArgs, pAppendInfo->pszMsgFlags,
pAppendInfo->ftReceived, pAppendInfo->lpstmMsg);
TraceError(hr);
}
break; // case icAPPEND_COMMAND
case icLIST_COMMAND:
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
hr = m_pTransport->List(pioNextCmd->wParam, pioNextCmd->lParam,
this, "", pioNextCmd->pszCmdArgs); // Reference is always blank
TraceError(hr);
break; // case icLIST_COMMAND
case icLSUB_COMMAND:
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
hr = m_pTransport->Lsub(pioNextCmd->wParam, pioNextCmd->lParam,
this, "", pioNextCmd->pszCmdArgs); // Reference is always blank
TraceError(hr);
break; // case icLSUB_COMMAND
case icCREATE_COMMAND:
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
hr = m_pTransport->Create(pioNextCmd->wParam, pioNextCmd->lParam,
this, pioNextCmd->pszCmdArgs);
TraceError(hr);
break; // case icCREATE_COMMAND
case icSUBSCRIBE_COMMAND:
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
hr = m_pTransport->Subscribe(pioNextCmd->wParam, pioNextCmd->lParam,
this, pioNextCmd->pszCmdArgs);
TraceError(hr);
break; // case icSUBSCRIBE_COMMAND
case icDELETE_COMMAND:
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
hr = m_pTransport->Delete(pioNextCmd->wParam, pioNextCmd->lParam,
this, pioNextCmd->pszCmdArgs);
TraceError(hr);
break; // case icDELETE_COMMAND
case icUNSUBSCRIBE_COMMAND:
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
hr = m_pTransport->Unsubscribe(pioNextCmd->wParam, pioNextCmd->lParam,
this, pioNextCmd->pszCmdArgs);
TraceError(hr);
break; // case icUNSUBSCRIBE_COMMAND
case icRENAME_COMMAND:
{
CRenameFolderInfo *pRenameInfo;
LPSTR pszOldFldrName;
IxpAssert(NULL != pioNextCmd->pszCmdArgs);
IxpAssert(NULL != pioNextCmd->lParam);
pRenameInfo = (CRenameFolderInfo *) pioNextCmd->lParam;
hr = m_pTransport->Rename(pioNextCmd->wParam, (LPARAM) pRenameInfo,
this, pRenameInfo->pszRenameCmdOldFldrPath, pioNextCmd->pszCmdArgs);
TraceError(hr);
} // case icRENAME_COMMAND
break; // case icRENAME_COMMAND
case icSTATUS_COMMAND:
{
DWORD dwCapabilities;
IxpAssert(FOLDERID_INVALID != (FOLDERID)pioNextCmd->lParam);
// Have to check if this is an IMAP4rev1 server. If not, FAIL the Status operation
hr = m_pTransport->Capability(&dwCapabilities);
if (SUCCEEDED(hr) && 0 == (dwCapabilities & IMAP_CAPABILITY_IMAP4rev1))
{
// Can't currently check unread count for non-IMAP4rev1 servers
hr = STORE_E_NOSERVERSUPPORT;
}
else
{
hr = m_pTransport->Status(pioNextCmd->wParam, pioNextCmd->lParam,
this, pioNextCmd->pszCmdArgs, "(MESSAGES UNSEEN)");
}
}
break;
default:
AssertSz(FALSE, "Someone queued an operation I can't handle!");
break;
}
// Handle any errors encountered above
if (FAILED(hr))
{
TraceResult(hr);
// Free any non-NULL lParam's and call IStoreCallback::OnComplete
if (0 == (dwFlags & snoDO_NOT_DISPOSE))
{
if ('\0' == m_szOperationDetails)
// Fill in error information: error propagation causes IStoreCallback::OnComplete call
LoadString(g_hLocRes, idsIMAPSendNextOpErrText, m_szOperationDetails, ARRAYSIZE(m_szOperationDetails));
DisposeOfWParamLParam(pioNextCmd->wParam, pioNextCmd->lParam, hr);
}
}
// Deallocate the imap operation
if (NULL != pioNextCmd->pszCmdArgs)
MemFree(pioNextCmd->pszCmdArgs);
delete pioNextCmd;
exit:
return hr;
}
//***************************************************************************
// Function: FlushOperationQueue
//
// Purpose:
// This function frees the entire contents of the IMAP operation queue.
// Usually used by the CIMAPSync destructor, and whenever an error occurs
// which would prevent the sending of queued IMAP operations (eg, login
// failure).
//
// Arguments:
// IMAP_SERVERSTATE issMaximum [in] - defines the maximum server state
// currently allowed in the queue. For instance, if a select failed,
// we would call FlushOperationQueue(issAuthenticated) to remove all
// commands that require issSelected as their minimum state. To remove
// all commands, pass in issNotConnected.
//***************************************************************************
void CIMAPSync::FlushOperationQueue(IMAP_SERVERSTATE issMaximum, HRESULT hrError)
{
IMAP_OPERATION *pioCurrent;
IMAP_OPERATION *pioPrev;
IxpAssert(((int) m_cRef) >= 0); // Can be called by destructor
pioPrev = NULL;
pioCurrent = m_pioNextOperation;
while (NULL != pioCurrent)
{
// Check if current command should be deleted
if (pioCurrent->issMinimum > issMaximum)
{
IMAP_OPERATION *pioDead;
HRESULT hr;
// Current command level exceeds the maximum. Unlink from queue and delete
pioDead = pioCurrent;
if (NULL == pioPrev)
{
// Dequeue from head of queue
m_pioNextOperation = pioCurrent->pioNextCommand;
pioCurrent = pioCurrent->pioNextCommand;
}
else
{
// Dequeue from mid/end of queue
pioPrev->pioNextCommand = pioCurrent->pioNextCommand;
pioCurrent = pioCurrent->pioNextCommand;
}
// Free any non-NULL lParam's and call IStoreCallback::OnComplete
if ('\0' == m_szOperationDetails)
// Fill in error information: error propagation causes IStoreCallback::OnComplete call
LoadString(g_hLocRes, idsIMAPSendNextOpErrText, m_szOperationDetails,
ARRAYSIZE(m_szOperationDetails));
DisposeOfWParamLParam(pioDead->wParam, pioDead->lParam, hrError);
if (NULL != pioDead->pszCmdArgs)
MemFree(pioDead->pszCmdArgs);
delete pioDead;
}
else
{
// Current command is within maximum level. Advance pointers
pioPrev = pioCurrent;
pioCurrent = pioCurrent->pioNextCommand;
}
} // while
} // FlushOperationQueue
//***************************************************************************
//***************************************************************************
IMAP_SERVERSTATE CIMAPSync::IMAPCmdToMinISS(IMAP_COMMAND icCommandID)
{
IMAP_SERVERSTATE issResult;
TraceCall("CIMAPSync::IMAPCmdToMinISS");
switch (icCommandID)
{
case icSELECT_COMMAND:
case icEXAMINE_COMMAND:
case icCREATE_COMMAND:
case icDELETE_COMMAND:
case icRENAME_COMMAND:
case icSUBSCRIBE_COMMAND:
case icUNSUBSCRIBE_COMMAND:
case icLIST_COMMAND:
case icLSUB_COMMAND:
case icAPPEND_COMMAND:
case icSTATUS_COMMAND:
issResult = issAuthenticated;
break;
case icCLOSE_COMMAND:
case icSEARCH_COMMAND:
case icFETCH_COMMAND:
case icSTORE_COMMAND:
case icCOPY_COMMAND:
issResult = issSelected;
break;
default:
AssertSz(FALSE, "What command are you trying to send?");
// *** FALL THROUGH ***
case icLOGOUT_COMMAND:
case icNOOP_COMMAND:
issResult = issNonAuthenticated;
break;
}
return issResult;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::GetNextOperation(IMAP_OPERATION **ppioOp)
{
HRESULT hr = S_OK;
IMAP_OPERATION *pioCurrent;
IMAP_OPERATION *pioPrev;
TraceCall("CIMAPSync::GetNextOperation");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != ppioOp);
pioPrev = NULL;
pioCurrent = m_pioNextOperation;
while (NULL != pioCurrent)
{
// Check if we are able to send the current command
if (pioCurrent->issMinimum <= m_issCurrent)
break;
// Advance pointers
pioPrev = pioCurrent;
pioCurrent = pioCurrent->pioNextCommand;
}
// Check if we found anything
if (NULL == pioCurrent)
{
hr = STORE_S_NOOP; // Nothing to send at the moment
goto exit;
}
// If we reached here, we found something. Dequeue operation.
*ppioOp = pioCurrent;
if (NULL == pioPrev)
{
// Dequeue from head of queue
m_pioNextOperation = pioCurrent->pioNextCommand;
}
else
{
// Dequeue from mid/end of queue
pioPrev->pioNextCommand = pioCurrent->pioNextCommand;
}
exit:
return hr;
}
//***************************************************************************
// Function: DisposeofWParamLParam
//
// Purpose:
// This function eliminates the wParam and lParam arguments of an IMAP
// operation in the event of failure.
//
// Arguments:
// WPARAM wParam - the wParam of the failed IMAP operation.
// LPARAM lPAram - the lParam of the failed IMAP operation.
// HRESULT hr - the error condition that caused the IMAP op failure
//***************************************************************************
void CIMAPSync::DisposeOfWParamLParam(WPARAM wParam, LPARAM lParam, HRESULT hr)
{
TraceCall("CIMAPSync::DisposeofWParamLParam");
IxpAssert(m_cRef > 0);
AssertSz(FAILED(hr), "If you didn't fail, why are you here?");
switch (wParam)
{
case tidCOPYMSGS:
case tidMOVEMSGS:
{
IMAP_COPYMOVE_INFO *pCopyMoveInfo;
// Notify the user that the operation has failed, and free the structure
pCopyMoveInfo = (IMAP_COPYMOVE_INFO *) lParam;
SafeMemFree(pCopyMoveInfo->pList);
pCopyMoveInfo->pCopyRange->Release();
delete pCopyMoveInfo;
break;
}
case tidBODYMSN:
case tidBODY:
LoadString(g_hLocRes, idsIMAPBodyFetchFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
NotifyMsgRecipients(lParam, fCOMPLETED, NULL, hr, m_szOperationProblem);
break; // case tidBODYMSN, tidBODY
case tidMARKMSGS:
{
MARK_MSGS_INFO *pMarkMsgInfo;
// Notify the user that the operation has failed, and free the structure
pMarkMsgInfo = (MARK_MSGS_INFO *) lParam;
SafeMemFree(pMarkMsgInfo->pList);
SafeRelease(pMarkMsgInfo->pMsgRange);
delete pMarkMsgInfo;
break;
}
break; // case tidMARKMSGS
case tidFETCH_NEW_HDRS:
IxpAssert(lParam == NULL);
break;
case tidFETCH_CACHED_FLAGS:
IxpAssert(lParam == NULL);
break;
case tidNOOP:
IxpAssert(lParam == NULL);
break;
case tidSELECTION:
IxpAssert(lParam != NULL);
break;
case tidUPLOADMSG:
{
APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) lParam;
SafeMemFree(pAppendInfo->pszMsgFlags);
SafeRelease(pAppendInfo->lpstmMsg);
delete pAppendInfo;
}
break;
case tidCREATE:
case tidCREATELIST:
case tidCREATESUBSCRIBE:
case tidSPECIALFLDRLIST:
case tidSPECIALFLDRLSUB:
case tidSPECIALFLDRSUBSCRIBE:
{
CREATE_FOLDER_INFO *pcfiCreateInfo;
pcfiCreateInfo = (CREATE_FOLDER_INFO *) lParam;
MemFree(pcfiCreateInfo->pszFullFolderPath);
if (NULL != pcfiCreateInfo->lParam)
{
switch (pcfiCreateInfo->pcoNextOp)
{
case PCO_NONE:
AssertSz(FALSE, "Expected NULL lParam. Check for memleak.");
break;
case PCO_FOLDERLIST:
AssertSz(FOLDERID_INVALID == (FOLDERID) pcfiCreateInfo->lParam,
"Expected FOLDERID_INVALID lParam. Check for memleak.");
break;
case PCO_APPENDMSG:
{
APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) pcfiCreateInfo->lParam;
SafeMemFree(pAppendInfo->pszMsgFlags);
SafeRelease(pAppendInfo->lpstmMsg);
delete pAppendInfo;
}
break;
default:
AssertSz(FALSE, "Unhandled CREATE_FOLDER_INFO lParam. Check for memleak.");
break;
} // switch
}
delete pcfiCreateInfo;
break;
}
case tidDELETEFLDR:
case tidDELETEFLDR_UNSUBSCRIBE:
MemFree(((DELETE_FOLDER_INFO *)lParam)->pszFullFolderPath);
MemFree((DELETE_FOLDER_INFO *)lParam);
break; // case tidDELETEFLDR_UNSUBSCRIBE
case tidSUBSCRIBE:
IxpAssert(NULL == lParam);
break;
case tidRENAME:
case tidRENAMESUBSCRIBE:
case tidRENAMELIST:
case tidRENAMERENAME:
case tidRENAMESUBSCRIBE_AGAIN:
case tidRENAMEUNSUBSCRIBE:
((CRenameFolderInfo *) lParam)->Release();
break;
case tidHIERARCHYCHAR_LIST_B:
case tidHIERARCHYCHAR_CREATE:
case tidHIERARCHYCHAR_LIST_C:
case tidHIERARCHYCHAR_DELETE:
case tidPREFIXLIST:
case tidPREFIX_HC:
case tidPREFIX_CREATE:
case tidFOLDERLIST:
case tidFOLDERLSUB:
case tidSTATUS:
break;
default:
AssertSz(NULL == lParam, "Is this a possible memory leak?");
break;
}
} // DisposeOfWParamLParam
//***************************************************************************
// Function: NotifyMsgRecipients
//
// Purpose:
// This function sends notifications to all registered recipients of a
// given message UID. Currently handles IMC_BODYAVAIL and IMC_ARTICLEPROG
// messages.
//
// Arguments:
// DWORD dwUID [in] - the UID identifying the message whose recipients
// are to be updated.
// BOOL fCompletion [in] - TRUE if we're done fetching the msg body.
// FALSE if we're still in the middle of fetching (progress indication)
// FETCH_BODY_PART *pFBPart [in] - fragment of FETCH body currently being
// downloaded. Should always be NULL if fCompletion is TRUE.
// HRESULT hrCompletion [in] - completion result. Should always be S_OK
// if fCompletion is FALSE.
// LPSTR pszDetails [in] - error message details for completion. Should
// always be NULL unless fCompletion is TRUE and hrCompletion is a
// failure code.
//***************************************************************************
void CIMAPSync::NotifyMsgRecipients(DWORD_PTR dwUID, BOOL fCompletion,
FETCH_BODY_PART *pFBPart,
HRESULT hrCompletion, LPSTR pszDetails)
{
HRESULT hrTemp; // For recording non-fatal errors
ADJUSTFLAGS flags;
MESSAGEIDLIST list;
TraceCall("CIMAPSync::NotifyMsgRecipients");
IxpAssert(m_cRef > 0);
IxpAssert(0 != dwUID);
AssertSz(NULL == pFBPart || FALSE == fCompletion, "pFBPart must be NULL if fCompletion TRUE!");
AssertSz(NULL != pFBPart || fCompletion, "pFBPart cannot be NULL if fCompletion FALSE!");
AssertSz(NULL == pszDetails || fCompletion, "pszDetails must be NULL if fCompletion FALSE!");
AssertSz(S_OK == hrCompletion || fCompletion, "hrCompletion must be S_OK if fCompletion FALSE!");
IxpAssert(m_pCurrentCB || fCompletion);
IxpAssert(m_pstmBody);
IxpAssert(m_idMessage || FALSE == fCompletion);
// If this is a failed completion, fill out a STOREERROR struct
if (fCompletion && FAILED(hrCompletion))
{
if (IS_INTRESOURCE(pszDetails))
{
// pszDetails is actually a string resource, so load it up
LoadString(g_hLocRes, PtrToUlong(pszDetails), m_szOperationDetails,
ARRAYSIZE(m_szOperationDetails));
pszDetails = m_szOperationDetails;
}
LoadString(g_hLocRes, idsIMAPDnldDlgDLFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
if (fCompletion)
{
if (SUCCEEDED(hrCompletion))
{
HRESULT hr;
hr = CommitMessageToStore(m_pFolder, NULL, m_idMessage, m_pstmBody);
if (FAILED(hr))
{
TraceResult(hr);
LoadString(g_hLocRes, idsErrSetMessageStreamFailed, m_szOperationProblem,
ARRAYSIZE(m_szOperationProblem));
}
else
m_faStream = 0;
}
else if (hrCompletion == STORE_E_EXPIRED)
{
list.cAllocated = 0;
list.cMsgs = 1;
list.prgidMsg = &m_idMessage;
flags.dwAdd = ARF_ARTICLE_EXPIRED;
flags.dwRemove = ARF_DOWNLOAD;
Assert(m_pFolder);
m_pFolder->SetMessageFlags(&list, &flags, NULL, NULL);
//m_pFolder->SetMessageStream(m_idMessage, m_pstmBody);
}
SafeRelease(m_pstmBody);
if (0 != m_faStream)
{
Assert(m_pFolder);
m_pFolder->DeleteStream(m_faStream);
m_faStream = 0;
}
}
else
{
DWORD dwCurrent;
DWORD dwTotal;
ULONG ulWritten;
// Write this fragment to the stream
IxpAssert(fbpBODY == pFBPart->lpFetchCookie1);
hrTemp = m_pstmBody->Write(pFBPart->pszData, pFBPart->dwSizeOfData, &ulWritten);
if (FAILED(hrTemp))
m_hrOperationResult = TraceResult(hrTemp); // Make sure we don't commit stream
else
IxpAssert(ulWritten == pFBPart->dwSizeOfData);
if (pFBPart->dwSizeOfData > 0)
m_fGotBody = TRUE;
// Indicate message download progress
if (pFBPart->dwTotalBytes > 0)
{
dwCurrent = pFBPart->dwOffset + pFBPart->dwSizeOfData;
dwTotal = pFBPart->dwTotalBytes;
m_pCurrentCB->OnProgress(SOT_GET_MESSAGE, dwCurrent, dwTotal, NULL);
}
}
} // NotifyMsgRecipients
//***************************************************************************
// Function: OnFolderExit
//
// Purpose:
// This function is called when a folder is exited (currently occurs only
// through a disconnect). It resets the module's folder-specific variables
// so that re-connection to the folder (or a different folder) cause
// carry-over of information from the previous session.
//***************************************************************************
void CIMAPSync::OnFolderExit(void)
{
HRESULT hrTemp;
TraceCall("CIMAPSync::OnFolderExit");
IxpAssert(m_cRef > 0);
m_dwMsgCount = 0;
m_fMsgCountValid = FALSE;
m_dwNumHdrsDLed = 0;
m_dwNumUnreadDLed = 0;
m_dwNumHdrsToDL = 0;
m_dwUIDValidity = 0;
m_dwSyncToDo = 0; // Leave m_dwSyncFolderFlags as-is, so we can re-sync on re-connect
m_dwHighestCachedUID = 0;
m_rwsReadWriteStatus = rwsUNINITIALIZED;
m_fNewMail = FALSE;
m_fInbox = FALSE;
m_fDidFullSync = FALSE;
m_idSelectedFolder = FOLDERID_INVALID;
// Clear MsgSeqNumToUID table
hrTemp = m_pTransport->ResetMsgSeqNumToUID();
TraceError(hrTemp);
}
//***************************************************************************
//***************************************************************************
void CIMAPSync::FillStoreError(LPSTOREERROR pErrorInfo, HRESULT hr,
DWORD dwSocketError, LPSTR pszProblem,
LPSTR pszDetails)
{
DWORD dwFlags = 0;
TraceCall("CIMAPSync::FillStoreError");
IxpAssert(((int) m_cRef) >= 0); // Can be called during destruction
IxpAssert(NULL != pErrorInfo);
// pszProblem/pszDetails = NULL means m_szOperationProblem/m_szOperationDetails already filled out
// Use defaults if any of the text fields are blank
if (NULL != pszProblem && IS_INTRESOURCE(pszProblem))
{
LoadString(g_hLocRes, PtrToUlong(pszProblem), m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
else if (NULL != pszProblem)
{
if ('\0' == *pszProblem)
LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
else
StrCpyN(m_szOperationProblem, pszProblem, ARRAYSIZE(m_szOperationProblem));
}
if (NULL != pszDetails && IS_INTRESOURCE(pszDetails))
{
LoadString(g_hLocRes, PtrToUlong(pszDetails), m_szOperationDetails, ARRAYSIZE(m_szOperationDetails));
}
else if (NULL != pszDetails)
{
if ('\0' == *pszDetails)
m_szOperationDetails[0] = '\0';
else
StrCpyN(m_szOperationDetails, pszDetails, ARRAYSIZE(m_szOperationDetails));
}
// If we are currently disconnected, it is unlikely that any additional operations
// should be sent to the IMAP server: there's likely a connection error or user cancellation.
if (STORE_E_OPERATION_CANCELED == hr || m_cfsPrevState <= CFSM_STATE_WAITFORCONN)
dwFlags |= SE_FLAG_FLUSHALL;
// Fill out the STOREERROR structure
ZeroMemory(pErrorInfo, sizeof(*pErrorInfo));
pErrorInfo->hrResult = hr;
pErrorInfo->uiServerError = 0; // No such thing in the IMAP protocol
pErrorInfo->hrServerError = S_OK;
pErrorInfo->dwSocketError = dwSocketError; // Oops, not propagated in IIMAPCallback::OnResponse
pErrorInfo->pszProblem = m_szOperationProblem;
pErrorInfo->pszDetails = m_szOperationDetails;
pErrorInfo->pszAccount = m_rInetServerInfo.szAccount;
pErrorInfo->pszServer = m_rInetServerInfo.szServerName;
pErrorInfo->pszUserName = m_rInetServerInfo.szUserName;
pErrorInfo->pszProtocol = "IMAP";
pErrorInfo->pszConnectoid = m_rInetServerInfo.szConnectoid;
pErrorInfo->rasconntype = m_rInetServerInfo.rasconntype;
pErrorInfo->ixpType = IXP_IMAP;
pErrorInfo->dwPort = m_rInetServerInfo.dwPort;
pErrorInfo->fSSL = m_rInetServerInfo.fSSL;
pErrorInfo->fTrySicily = m_rInetServerInfo.fTrySicily;
pErrorInfo->dwFlags = dwFlags;
}
//***************************************************************************
// Function: Fill_MESSAGEINFO
//
// Purpose:
// This function is no longer largely based on (shamelessly stolen) code
// from MsgIn.Cpp's. As Brett rewrote it to use MIMEOLE. Fingers crossed, kids...
// This function takes a FETCH_CMD_RESULTS_EX struct (which MUST
// have a header or a body) and fills out a MESSAGEINFO structure based on
// the information in the header.
//
// Arguments:
// const FETCH_CMD_RESULTS_EX *pFetchResults [in] - contains the results of
// a FETCH response. This MUST contain either a header or a body.
// MESSAGEINFO *pMsgInfo [out] - this function fills out the given
// MESSAGEINFO with the information from the FETCH response. Note that
// this function zeroes the destination, so the caller need not.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::Fill_MESSAGEINFO(const FETCH_CMD_RESULTS_EX *pFetchResults,
MESSAGEINFO *pMsgInfo)
{
// Locals
HRESULT hr = S_OK;
LPSTR lpsz;
IMimePropertySet *pPropertySet = NULL;
IMimeAddressTable *pAddrTable = NULL;
ADDRESSPROPS rAddress;
IMSGPRIORITY impPriority;
PROPVARIANT rVariant;
LPSTREAM lpstmRFC822;
TraceCall("CIMAPSync::Fill_MESSAGEINFO");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pFetchResults);
IxpAssert(NULL != pMsgInfo);
IxpAssert(TRUE == pFetchResults->bUID);
// Initialize the destination
ZeroMemory(pMsgInfo, sizeof(MESSAGEINFO));
// Fill in fields that need no thought
pMsgInfo->pszAcctId = PszDupA(m_pszAccountID);
pMsgInfo->pszAcctName = PszDupA(m_szAccountName);
// Deal with the easy FETCH_CMD_RESULTS_EX fields, first
if (pFetchResults->bUID)
pMsgInfo->idMessage = (MESSAGEID)((ULONG_PTR)pFetchResults->dwUID);
if (pFetchResults->bMsgFlags)
pMsgInfo->dwFlags = DwConvertIMAPtoARF(pFetchResults->mfMsgFlags);
if (pFetchResults->bRFC822Size)
pMsgInfo->cbMessage = pFetchResults->dwRFC822Size;
if (pFetchResults->bInternalDate)
pMsgInfo->ftReceived = pFetchResults->ftInternalDate;
if (pFetchResults->bEnvelope)
{
hr= ReadEnvelopeFields(pMsgInfo, pFetchResults);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
// Now, it's time to parse the header (or partial header, if we only asked for certain fields)
lpstmRFC822 = (LPSTREAM) pFetchResults->lpFetchCookie2;
if (NULL == lpstmRFC822)
{
if (FALSE == pFetchResults->bEnvelope)
hr = TraceResult(E_FAIL); // Hmm, no envelope, no header... sounds like failure!
goto exit;
}
hr = MimeOleCreatePropertySet(NULL, &pPropertySet);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = HrRewindStream(lpstmRFC822);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// call IPS::Load on the header, and get the parsed stuff out.
hr = pPropertySet->Load(lpstmRFC822);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Don't ask for the following basic (non-derived) fields unless we did NOT get an envelope
if (FALSE == pFetchResults->bEnvelope)
{
// Don't bother tracing non-fatal errors, as not all msgs will have all properties
hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_MESSAGEID), NOFLAGS, &lpsz);
if (SUCCEEDED(hr))
{
pMsgInfo->pszMessageId = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
}
hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_SUBJECT), NOFLAGS, &lpsz);
if (SUCCEEDED(hr))
{
pMsgInfo->pszSubject = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
}
hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_FROM), NOFLAGS, &lpsz);
TraceError(hr); // Actually, this is odd
if (SUCCEEDED(hr))
{
pMsgInfo->pszFromHeader = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
}
rVariant.vt = VT_FILETIME;
hr = pPropertySet->GetProp(PIDTOSTR(PID_ATT_SENTTIME), 0, &rVariant);
if (SUCCEEDED(hr))
CopyMemory(&pMsgInfo->ftSent, &rVariant.filetime, sizeof(FILETIME));
}
// The following fields are not normally supplied via envelope
// [PaulHi] 6/10/99
// !!!Note that the IMAP server will not include these properties in the header download
// unless they are listed in the request string. See cszIMAPFetchNewHdrsI4r1 string
// declared above!!!
hr = MimeOleGetPropA(pPropertySet, STR_HDR_XMSOESREC, NOFLAGS, &lpsz);
TraceError(hr); // Actually, this is odd
if (SUCCEEDED(hr))
{
pMsgInfo->pszMSOESRec = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
}
hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_REFS), NOFLAGS, &lpsz);
if (SUCCEEDED(hr))
{
pMsgInfo->pszReferences = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
}
hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_XREF), NOFLAGS, &lpsz);
if (SUCCEEDED(hr))
{
pMsgInfo->pszXref = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
}
rVariant.vt = VT_UI4;
hr = pPropertySet->GetProp(PIDTOSTR(PID_ATT_PRIORITY), 0, &rVariant);
if (SUCCEEDED(hr))
// Convert IMSGPRIORITY to ARF_PRI_*
pMsgInfo->wPriority = (WORD)rVariant.ulVal;
// Make sure every basic (ie, non-derived) string field has SOMETHING
if (NULL == pMsgInfo->pszMessageId)
pMsgInfo->pszMessageId = PszDupA(c_szEmpty);
if (NULL == pMsgInfo->pszSubject)
pMsgInfo->pszSubject = PszDupA(c_szEmpty);
if (NULL == pMsgInfo->pszFromHeader)
pMsgInfo->pszFromHeader = PszDupA(c_szEmpty);
if (NULL == pMsgInfo->pszReferences)
pMsgInfo->pszReferences = PszDupA(c_szEmpty);
if (NULL == pMsgInfo->pszXref)
pMsgInfo->pszXref = PszDupA (c_szEmpty);
// Now that every basic string field is non-NULL, calculate DERIVED str fields
pMsgInfo->pszNormalSubj = SzNormalizeSubject(pMsgInfo->pszSubject);
if (NULL == pMsgInfo->pszNormalSubj)
pMsgInfo->pszNormalSubj = pMsgInfo->pszSubject;
// Only calculate "To" and "From" if we did NOT get an envelope
if (FALSE == pFetchResults->bEnvelope)
{
// Get an address table
hr = pPropertySet->BindToObject(IID_IMimeAddressTable, (LPVOID *)&pAddrTable);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Split "From" field into a display name and email name
rAddress.dwProps = IAP_FRIENDLY | IAP_EMAIL;
hr = pAddrTable->GetSender(&rAddress);
if (SUCCEEDED(hr))
{
pMsgInfo->pszDisplayFrom = rAddress.pszFriendly;
pMsgInfo->pszEmailFrom = rAddress.pszEmail;
}
// Split "To" field into a display name and email name
hr = pAddrTable->GetFormat(IAT_TO, AFT_DISPLAY_FRIENDLY, &lpsz);
if (SUCCEEDED(hr))
{
pMsgInfo->pszDisplayTo = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
}
hr = pAddrTable->GetFormat(IAT_TO, AFT_DISPLAY_EMAIL, &lpsz);
if (SUCCEEDED(hr))
{
pMsgInfo->pszEmailTo = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
}
}
// If "Newsgroups" field is present, it supercedes the "To" field
hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_NEWSGROUPS), NOFLAGS, &lpsz);
if (SUCCEEDED(hr))
{
SafeMemFree(pMsgInfo->pszDisplayTo); // Free what's already there
pMsgInfo->pszDisplayTo = PszDupA(lpsz);
SafeMimeOleFree(lpsz);
pMsgInfo->dwFlags |= ARF_NEWSMSG;
}
// Make sure that all derived fields are non-NULL
if (NULL == pMsgInfo->pszDisplayFrom)
pMsgInfo->pszDisplayFrom = PszDupA(c_szEmpty);
if (NULL == pMsgInfo->pszEmailFrom)
pMsgInfo->pszEmailFrom = PszDupA(c_szEmpty);
if (NULL == pMsgInfo->pszDisplayTo)
pMsgInfo->pszDisplayTo = PszDupA(c_szEmpty);
// OK, if we get to here, we've decided to live with errors. Suppress errors.
hr = S_OK;
exit:
// Cleanup
SafeRelease(pPropertySet);
SafeRelease(pAddrTable);
// Done
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::ReadEnvelopeFields(MESSAGEINFO *pMsgInfo,
const FETCH_CMD_RESULTS_EX *pFetchResults)
{
HRESULT hrResult;
PROPVARIANT rDecoded;
// (1) Date
pMsgInfo->ftSent = pFetchResults->ftENVDate;
// (2) Subject
rDecoded.vt = VT_LPSTR;
if (FAILED(MimeOleDecodeHeader(NULL, pFetchResults->pszENVSubject, &rDecoded, NULL)))
pMsgInfo->pszSubject = PszDupA(pFetchResults->pszENVSubject);
else
pMsgInfo->pszSubject = rDecoded.pszVal;
if (NULL == pMsgInfo->pszSubject)
{
hrResult = TraceResult(E_OUTOFMEMORY);
goto exit;
}
// (3) From
hrResult = ConcatIMAPAddresses(&pMsgInfo->pszDisplayFrom, &pMsgInfo->pszEmailFrom,
pFetchResults->piaENVFrom);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
// (4) Sender: IGNORE
// (5) ReplyTo: IGNORE
// (6) To
hrResult = ConcatIMAPAddresses(&pMsgInfo->pszDisplayTo, &pMsgInfo->pszEmailTo,
pFetchResults->piaENVTo);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
// (7) Cc: IGNORE
// (8) Bcc: IGNORE
// (9) In-Reply-To: IGNORE
// (10) MessageID
pMsgInfo->pszMessageId = PszDupA(pFetchResults->pszENVMessageID);
if (NULL == pMsgInfo->pszMessageId)
{
hrResult = TraceResult(E_OUTOFMEMORY);
goto exit;
}
exit:
return hrResult;
} // ReadEnvelopeFields
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::ConcatIMAPAddresses(LPSTR *ppszDisplay, LPSTR *ppszEmailAddr,
IMAPADDR *piaIMAPAddr)
{
HRESULT hrResult = S_OK;
CByteStream bstmDisplay;
CByteStream bstmEmail;
BOOL fPrependDisplaySeparator = FALSE;
BOOL fPrependEmailSeparator = FALSE;
// Initialize output
if (NULL != ppszDisplay)
*ppszDisplay = NULL;
if (NULL != ppszEmailAddr)
*ppszEmailAddr = NULL;
// Loop through all IMAP addresses
while (NULL != piaIMAPAddr)
{
// Concatenate current email address to list of email addresses
// Do email address first to allow substitution of email addr for missing display name
if (NULL != ppszEmailAddr)
{
if (FALSE == fPrependEmailSeparator)
fPrependEmailSeparator = TRUE;
else
{
hrResult = bstmEmail.Write(c_szSemiColonSpace, 2, NULL);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
}
hrResult = ConstructIMAPEmailAddr(bstmEmail, piaIMAPAddr);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
} // if (NULL != ppszEmailAddr)
// Concatenate current display name to list of display names
if (NULL != ppszDisplay)
{
PROPVARIANT rDecoded;
LPSTR pszName;
int iLen;
if (FALSE == fPrependDisplaySeparator)
fPrependDisplaySeparator = TRUE;
else
{
hrResult = bstmDisplay.Write(c_szSemiColonSpace, 2, NULL);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
}
PropVariantInit(&rDecoded);
rDecoded.vt = VT_LPSTR;
if (FAILED(MimeOleDecodeHeader(NULL, piaIMAPAddr->pszName, &rDecoded, NULL)))
pszName = StrDupA(piaIMAPAddr->pszName);
else
pszName = rDecoded.pszVal;
if(FAILED(hrResult = MimeOleUnEscapeStringInPlace(pszName)))
TraceResult(hrResult);
iLen = lstrlen(pszName);
if (0 != iLen)
hrResult = bstmDisplay.Write(pszName, iLen, NULL);
else
// Friendly name is not available! Substitute email address
hrResult = ConstructIMAPEmailAddr(bstmDisplay, piaIMAPAddr);
if (rDecoded.pszVal)
MemFree(rDecoded.pszVal); // Probably should be SafeMimeOleFree, but we also ignore above
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
} // if (NULL != ppszDisplay)
// Advance pointer
piaIMAPAddr = piaIMAPAddr->pNext;
} // while
// Convert stream to buffer for return to caller
if (NULL != ppszDisplay)
{
hrResult = bstmDisplay.HrAcquireStringA(NULL, ppszDisplay, ACQ_DISPLACE);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
}
if (NULL != ppszEmailAddr)
{
hrResult = bstmEmail.HrAcquireStringA(NULL, ppszEmailAddr, ACQ_DISPLACE);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
}
exit:
return hrResult;
} // ConcatIMAPAddresses
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::ConstructIMAPEmailAddr(CByteStream &bstmOut, IMAPADDR *piaIMAPAddr)
{
HRESULT hrResult;
hrResult = bstmOut.Write(piaIMAPAddr->pszMailbox, lstrlen(piaIMAPAddr->pszMailbox), NULL);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
hrResult = bstmOut.Write(c_szAt, 1, NULL);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
hrResult = bstmOut.Write(piaIMAPAddr->pszHost, lstrlen(piaIMAPAddr->pszHost), NULL);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
exit:
return hrResult;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::_SyncHeader(void)
{
HRESULT hr = S_OK;
char szFetchArgs[200];
BOOL fNOOP = TRUE;
TraceCall("CIMAPSync::_SyncHeader");
IxpAssert(m_cRef > 0);
// Look at the flags to determine the next operation
if (SYNC_FOLDER_NEW_HEADERS & m_dwSyncToDo)
{
// Remove this flag, as we are handling it now
m_dwSyncToDo &= ~(SYNC_FOLDER_NEW_HEADERS);
// Check if there are any new messages to retrieve
// Retrieve new headers iff > 0 msgs in this mailbox (Cyrus bug: sending
// UID FETCH in empty mailbox results in terminated connection)
// (NSCP v2.0 bug: no EXISTS resp when SELECT issued from selected state)
if ((m_dwMsgCount > 0 || FALSE == m_fMsgCountValid) &&
(FALSE == m_fDidFullSync || m_dwNumNewMsgs > 0))
{
DWORD dwCapability;
// No need to send NOOP anymore
m_dwSyncToDo &= ~(SYNC_FOLDER_NOOP);
// New messages available! Send FETCH to retrieve new headers
hr = GetHighestCachedMsgID(m_pFolder, &m_dwHighestCachedUID);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = m_pTransport->Capability(&dwCapability);
if (SUCCEEDED(dwCapability) && (IMAP_CAPABILITY_IMAP4rev1 & dwCapability))
wnsprintf(szFetchArgs, ARRAYSIZE(szFetchArgs), cszIMAPFetchNewHdrsI4r1, m_dwHighestCachedUID + 1);
else
wnsprintf(szFetchArgs, ARRAYSIZE(szFetchArgs), cszIMAPFetchNewHdrsI4, m_dwHighestCachedUID + 1);
hr = m_pTransport->Fetch(tidFETCH_NEW_HDRS, NULL, this,
NULL, USE_UIDS, szFetchArgs); // We always use UIDs
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
else
ResetStatusCounts();
// Reset progress indicator variables
m_dwNumHdrsDLed = 0;
m_dwNumUnreadDLed = 0;
m_dwNumHdrsToDL = m_dwNumNewMsgs;
m_dwNumNewMsgs = 0; // We're handling this now
m_fNewMail = FALSE;
m_lSyncFolderRefCount += 1;
fNOOP = FALSE;
goto exit; // Limit to one operation at a time, exit function now
}
}
if (SYNC_FOLDER_CACHED_HEADERS & m_dwSyncToDo)
{
// Remove this flag, as we are handling it now
m_dwSyncToDo &= ~(SYNC_FOLDER_CACHED_HEADERS);
// Check if we have any cached headers, and if we've already done flag update
if (0 == m_dwHighestCachedUID)
{
// Either m_dwHighestCachedUID was never loaded, or it really is zero. Check.
hr = GetHighestCachedMsgID(m_pFolder, &m_dwHighestCachedUID);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
if (FALSE == m_fDidFullSync && 0 != m_dwHighestCachedUID)
{
// No need to send NOOP anymore
m_dwSyncToDo &= ~(SYNC_FOLDER_NOOP);
wnsprintf(szFetchArgs, ARRAYSIZE(szFetchArgs), cszIMAPFetchCachedFlags, m_dwHighestCachedUID);
hr = m_pTransport->Fetch(tidFETCH_CACHED_FLAGS, NULL, this,
NULL, USE_UIDS, szFetchArgs); // We always use UIDs
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
else
ResetStatusCounts();
m_lSyncFolderRefCount += 1;
fNOOP = FALSE;
goto exit; // Limit to one operation at a time, exit function now
}
}
if (SYNC_FOLDER_PURGE_DELETED & m_dwSyncToDo)
{
// Remove the purge flag. Also, no need to send NOOP anymore since EXISTS
// and FETCH responses can be sent during EXPUNGE
m_dwSyncToDo &= ~(SYNC_FOLDER_PURGE_DELETED | SYNC_FOLDER_NOOP);
m_dwSyncFolderFlags &= ~(SYNC_FOLDER_PURGE_DELETED); // Not a standing order
hr = m_pTransport->Expunge(tidEXPUNGE, 0, this);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
else
ResetStatusCounts();
fNOOP = FALSE;
m_lSyncFolderRefCount += 1;
goto exit; // Limit to one operation at a time, exit function now
}
// If we reached this point, ping svr for new mail/cached hdr updates
// New mail/cached msg updates will be handled like any other unilateral response
if (SYNC_FOLDER_NOOP & m_dwSyncToDo)
{
// Remove these flags, as we are handling it now
m_dwSyncToDo &= ~(SYNC_FOLDER_NOOP);
IxpAssert(0 == (m_dwSyncToDo & (SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS)));
hr = m_pTransport->Noop(tidNOOP, NULL, this);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
else
ResetStatusCounts();
fNOOP = FALSE;
m_lSyncFolderRefCount += 1;
goto exit; // Limit to one operation at a time, exit function now
}
// Check if we had nothing left to do
if (fNOOP)
hr = STORE_S_NOOP;
exit:
return hr;
} // _SyncHeader
//***************************************************************************
//***************************************************************************
void CIMAPSync::ResetStatusCounts(void)
{
HRESULT hrTemp;
FOLDERINFO fiFolderInfo;
// We're about to do a full synchronization, so restore unread counts to
// pre-STATUS response levels
hrTemp = m_pStore->GetFolderInfo(m_idSelectedFolder, &fiFolderInfo);
TraceError(hrTemp);
if (SUCCEEDED(hrTemp))
{
if (0 != fiFolderInfo.dwStatusMsgDelta || 0 != fiFolderInfo.dwStatusUnreadDelta)
{
// Make sure that we never cause counts to dip below 0
if (fiFolderInfo.dwStatusMsgDelta > fiFolderInfo.cMessages)
fiFolderInfo.dwStatusMsgDelta = fiFolderInfo.cMessages;
if (fiFolderInfo.dwStatusUnreadDelta > fiFolderInfo.cUnread)
fiFolderInfo.dwStatusUnreadDelta = fiFolderInfo.cUnread;
fiFolderInfo.cMessages -= fiFolderInfo.dwStatusMsgDelta;
fiFolderInfo.cUnread -= fiFolderInfo.dwStatusUnreadDelta;
fiFolderInfo.dwStatusMsgDelta = 0;
fiFolderInfo.dwStatusUnreadDelta = 0;
Assert((LONG)fiFolderInfo.cMessages >= 0);
Assert((LONG)fiFolderInfo.cUnread >= 0);
hrTemp = m_pStore->UpdateRecord(&fiFolderInfo);
TraceError(hrTemp);
}
m_pStore->FreeRecord(&fiFolderInfo);
}
} // ResetStatusCounts
//***************************************************************************
// Function: CheckUIDValidity
//
// Purpose:
// This function checks the value in m_dwUIDValidity against the
// UIDValidity in the message cache for this folder. If the two match, no
// action is taken. Otherwise, the message cache is emptied.
//***************************************************************************
HRESULT CIMAPSync::CheckUIDValidity(void)
{
FOLDERUSERDATA fudUserData;
HRESULT hr;
TraceCall("CIMAPSync::CheckUIDValidity");
IxpAssert(m_cRef > 0);
// Load in UIDValidity of cache file
hr = m_pFolder->GetUserData(&fudUserData, sizeof(fudUserData));
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Is our current cache file invalid?
if (m_dwUIDValidity == fudUserData.dwUIDValidity)
goto exit; // We're the same as ever
// If we reached this point, the UIDValidity has changed
// Take out the cache
hr = m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, NULL, NULL, NULL);
if (FAILED(hr))
{
TraceError(hr);
goto exit;
}
// Write the new UIDValidity to the cache
fudUserData.dwUIDValidity = m_dwUIDValidity;
hr = m_pFolder->SetUserData(&fudUserData, sizeof(fudUserData));
if (FAILED(hr))
{
TraceError(hr);
goto exit;
}
exit:
return hr;
}
//***************************************************************************
// Function: SyncDeletedMessages
//
// Purpose:
// This function is called after the message cache is filled with all of
// the headers on the IMAP server (for this folder). This function deletes
// all messages from the message cache which no longer exist on the server.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::SyncDeletedMessages(void)
{
HRESULT hr;
DWORD *pdwMsgSeqNumToUIDArray = NULL;
DWORD *pdwCurrentServerUID;
DWORD *pdwLastServerUID;
ULONG_PTR ulCurrentCachedUID;
DWORD dwHighestMsgSeqNum;
HROWSET hRowSet = HROWSET_INVALID;
HLOCK hLockNotify=NULL;
MESSAGEINFO miMsgInfo = {0};
TraceCall("CIMAPSync::SyncDeletedMessages");
IxpAssert(m_cRef > 0);
// First, check for case where there are NO messages on server
hr = m_pTransport->GetMsgSeqNumToUIDArray(&pdwMsgSeqNumToUIDArray, &dwHighestMsgSeqNum);
if (FAILED(hr))
{
TraceResult(hr);
pdwMsgSeqNumToUIDArray = NULL; // Just in case
goto exit;
}
if (0 == dwHighestMsgSeqNum)
{
// No messages on server! Blow away the entire message cache
hr = m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, NULL, NULL, NULL);
TraceError(hr);
goto exit;
}
// If we've reached this point, there are messages on the server and thus
// we must delete messages from the cache which are no longer on server
hr = m_pFolder->CreateRowset(IINDEX_PRIMARY, NOFLAGS, &hRowSet);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = m_pFolder->QueryRowset(hRowSet, 1, (void **)&miMsgInfo, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
else if (S_OK != hr)
{
// There are 0 messages in cache. Our work here is done
IxpAssert(S_FALSE == hr);
goto exit;
}
// This forces all notifications to be queued (this is good since you do segmented deletes)
m_pFolder->LockNotify(0, &hLockNotify);
// Step through each UID in the cache and delete those which do not exist
// in our Msg Seq Num -> UID table (which holds all UIDs currently on server)
pdwCurrentServerUID = pdwMsgSeqNumToUIDArray;
pdwLastServerUID = pdwMsgSeqNumToUIDArray + dwHighestMsgSeqNum - 1;
while (S_OK == hr)
{
ulCurrentCachedUID = (ULONG_PTR) miMsgInfo.idMessage;
// Advance pdwCurrentServerUID so its value is always >= ulCurrentCachedUID
while (pdwCurrentServerUID < pdwLastServerUID &&
ulCurrentCachedUID > *pdwCurrentServerUID)
pdwCurrentServerUID += 1;
// If *pdwCurrentServerUID != ulCurrentCachedUID, the message in our
// cache has been deleted from the server
if (ulCurrentCachedUID != *pdwCurrentServerUID)
{
MESSAGEIDLIST midList;
MESSAGEID mid;
// This message in our cache has been deleted from the server. Nuke it.
// $REVIEW: Would probably be more efficient if we constructed MESSAGEID list
// and deleted whole thing at once, but ask me again when I have time
mid = (MESSAGEID) ulCurrentCachedUID;
midList.cAllocated = 0;
midList.cMsgs = 1;
midList.prgidMsg = &mid;
hr = m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, &midList, NULL, NULL);
TraceError(hr); // Record error but otherwise continue
}
// Advance current cached UID
m_pFolder->FreeRecord(&miMsgInfo);
hr = m_pFolder->QueryRowset(hRowSet, 1, (void **)&miMsgInfo, NULL);
}
IxpAssert(pdwCurrentServerUID <= pdwLastServerUID);
exit:
m_pFolder->UnlockNotify(&hLockNotify);
if (HROWSET_INVALID != hRowSet)
{
HRESULT hrTemp;
// Record but otherwise ignore error
hrTemp = m_pFolder->CloseRowset(&hRowSet);
TraceError(hrTemp);
}
if (NULL != pdwMsgSeqNumToUIDArray)
MemFree(pdwMsgSeqNumToUIDArray);
return hr;
}
#define CMAX_DELETE_SEARCH_BLOCK 50
HRESULT CIMAPSync::DeleteHashedFolders(IHashTable *pHash)
{
ULONG cFound=0;
LPVOID *rgpv;
TraceCall("CIMAPSync::DeleteHashedFolders");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pHash);
pHash->Reset();
while (SUCCEEDED(pHash->Next(CMAX_DELETE_SEARCH_BLOCK, &rgpv, &cFound)))
{
while(cFound--)
{
HRESULT hrTemp;
hrTemp = DeleteFolderFromCache((FOLDERID)rgpv[cFound], fNON_RECURSIVE);
TraceError(hrTemp);
}
SafeMemFree(rgpv);
}
return S_OK;
}
//***************************************************************************
// Function: DeleteFolderFromCache
//
// Purpose:
// This function attempts to delete the specified folder from the
// folder cache. If the folder is a leaf folder, it may be deleted immediately.
// If the folder is an internal node, this function marks the folder for
// deletion, and deletes the internal node when it no longer has children.
// Regardless of whether the folder node is removed from the folder cache,
// the message cache for the given folder is blown away.
//
// Arguments:
// FOLDERID idFolder [in] - the folder which you want to delete.
// BOOL fRecursive [in] - TRUE if we should delete all child folders of the
// victim. If FALSE, the victim is deleted only if it has no children.
// Otherwise the victim is marked as \NoSelect and non-existent.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::DeleteFolderFromCache(FOLDERID idFolder, BOOL fRecursive)
{
HRESULT hr;
HRESULT hrTemp;
FOLDERINFO fiFolderInfo;
BOOL fFreeInfo = FALSE;
TraceCall("CIMAPSync::DeleteFolderFromCache");
// Check args and codify assumptions
IxpAssert(m_cRef > 0);
IxpAssert(FOLDERID_INVALID != idFolder);
// Get some info about the node
hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
if (FAILED(hr))
{
if (DB_E_NOTFOUND == hr)
hr = S_OK; // Deletion target already gone, don't confuse user out w/ err msgs
else
TraceResult(hr);
goto exit;
}
fFreeInfo = TRUE;
// OK, now we can get rid of the foldercache node based on the following rules:
// 1) Non-listing of an interior node must not remove its inferiors: the interior node
// just becomes \NoSelect for us, and we delete it once it loses its children.
// 2) Deletion of a leaf node removes the node and any deleted parents. (If a
// parent is deleted, we keep it around until it has no kids.)
// 3) fRecursive TRUE means take no prisoners.
// Check if we need to recurse on the children
if (fRecursive)
{
IEnumerateFolders *pEnum;
FOLDERINFO fiChildInfo={0};
if (SUCCEEDED(m_pStore->EnumChildren(idFolder, fUNSUBSCRIBE, &pEnum)))
{
while (S_OK == pEnum->Next(1, &fiChildInfo, NULL))
{
hr = DeleteFolderFromCache(fiChildInfo.idFolder, fRecursive);
if (FAILED(hr))
{
TraceResult(hr);
break;
}
m_pStore->FreeRecord(&fiChildInfo);
}
m_pStore->FreeRecord(&fiChildInfo);
pEnum->Release();
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Re-load the current folder node
m_pStore->FreeRecord(&fiFolderInfo);
hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
}
// Is this an interior node?
if (FOLDER_HASCHILDREN & fiFolderInfo.dwFlags)
{
IMessageFolder *pFolder;
// It's an interior node. Awwwww, no nukes... make it \NoSelect,
// and mark it for deletion as soon as it loses its kids
fiFolderInfo.dwFlags |= FOLDER_NOSELECT | FOLDER_NONEXISTENT;
fiFolderInfo.cMessages = 0;
fiFolderInfo.cUnread = 0;
fiFolderInfo.dwStatusMsgDelta = 0;
fiFolderInfo.dwStatusUnreadDelta = 0;
hr = m_pStore->UpdateRecord(&fiFolderInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Plow the associated message cache
hrTemp = m_pStore->OpenFolder(fiFolderInfo.idFolder, NULL, NOFLAGS, &pFolder);
TraceError(hrTemp);
if (SUCCEEDED(hrTemp))
{
hrTemp = pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, NULL, NULL, NULL);
TraceError(hrTemp);
pFolder->Release();
}
}
else
{
// It's a leaf node. Nuke it, AND its family. DeleteLeafFolder fills in
// fiFolderInfo.idParent for use in RecalculateParentFlags call (no longer called)
fiFolderInfo.idParent = idFolder;
hr = DeleteLeafFolder(&fiFolderInfo.idParent);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
exit:
if (fFreeInfo)
m_pStore->FreeRecord(&fiFolderInfo);
return hr;
}
//***************************************************************************
// Function: DeleteLeafFolder
//
// Purpose:
// This function is used by DeleteFolderFromCache to delete a leaf folder.
// More than just a leaf blower, this function also checks if the parents of
// the given leaf node can be deleted.
//
// The reason we keep folder nodes around even though they haven't been
// listed is that it is possible on some IMAP servers to create folders whose
// parents aren't listed. For instance, "CREATE foo/bar" might not create foo,
// but "foo/bar" will be there. There has to be SOME path to that node, so
// when foo/bar goes, you can bet that we'll want to get rid of our "foo".
//
// Arguments:
// FOLDERID *pidCurrent [in/out] - pass in the HFOLDER identifying the leaf
// node to delete. The function returns a pointer to the closest existing
// ancestor of the deleted node (several parent nodes may be deleted).
//
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::DeleteLeafFolder(FOLDERID *pidCurrent)
{
HRESULT hr;
BOOL fFirstFolder;
FOLDERINFO fiFolderInfo;
// Check args and codify assumptions
TraceCall("CIMAPSync::DeleteLeafFolder");
IxpAssert(m_cRef > 0);
// Initialize variables
fFirstFolder = TRUE;
// Loop until the folder is not a deletion candidate
while (FOLDERID_INVALID != *pidCurrent && FOLDERID_ROOT != *pidCurrent &&
m_idIMAPServer != *pidCurrent)
{
// Get the dirt on this node
hr = m_pStore->GetFolderInfo(*pidCurrent, &fiFolderInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Check if this folder is a deletion candidate. To be a deletion candidate,
// it must either be the first folder we see (we assume the caller gave us a
// leaf node to delete), or marked for deletion AND have no children left.
if (FALSE == fFirstFolder && (0 == (FOLDER_NONEXISTENT & fiFolderInfo.dwFlags) ||
(FOLDER_HASCHILDREN & fiFolderInfo.dwFlags)))
{
m_pStore->FreeRecord(&fiFolderInfo);
break;
}
// We've got some deletion to do
// Unlink the leaf folder node from its parent folder
AssertSz(0 == (FOLDER_HASCHILDREN & fiFolderInfo.dwFlags),
"Hey, what's the idea, orphaning child nodes?");
hr = m_pStore->DeleteFolder(fiFolderInfo.idFolder,
DELETE_FOLDER_NOTRASHCAN | DELETE_FOLDER_DELETESPECIAL, NOSTORECALLBACK);
if (FAILED(hr))
{
m_pStore->FreeRecord(&fiFolderInfo);
TraceResult(hr);
goto exit;
}
// Next stop: your mama
*pidCurrent = fiFolderInfo.idParent;
m_pStore->FreeRecord(&fiFolderInfo);
fFirstFolder = FALSE;
}
exit:
return hr;
}
//***************************************************************************
// Function: AddFolderToCache
//
// Purpose:
// This function saves the given folder (fresh from
// _OnMailBoxList) to the folder cache. This code used to live
// in _OnMailBoxList but, that function got too big after I
// added hierarchy determination code.
//
// Arguments:
// LPSTR pszMailboxName [in] - name of the mailbox as returned by LIST/LSUB
// IMAP_MBOXFLAGS [in] - flags of mailbox as returned by LIST/LSUB
// char cHierarchyChar [in] - hierarchy char returned by LIST/LSUB
// DWORD dwAFTCFlags [in] - Set the following flags:
// AFTC_SUBSCRIBED if folder is subscribed (eg, returned via LSUB)
// AFTC_KEEPCHILDRENKNOWN to suppress removal of FOLDER_CHILDRENKNOWN
// AFTC_NOTSUBSCRIBED if folder is no longer subscribed (NEVER set via
// LIST, but instead at end of successful UNSUBSCRIBE command)
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::AddFolderToCache(LPSTR pszMailboxName,
IMAP_MBOXFLAGS imfMboxFlags,
char cHierarchyChar, DWORD dwAFTCFlags,
FOLDERID *pFolderID, SPECIALFOLDER sfType)
{
HRESULT hr;
BOOL bResult;
ADD_HIER_FLDR_OPTIONS ahfo;
BOOL fValidPrefix;
TraceCall("CIMAPSync::AddFolderToCache");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pszMailboxName);
IxpAssert(NULL != pFolderID);
// Create or find a folder node for this folder name
// Fill in foldercache props. INBOX is always treated as a subscribed folder
// Add new IMAP mbox flags, remove all IMAP mbox flags that we're not adding
ahfo.sfType = sfType;
ahfo.ffFlagAdd = DwConvertIMAPMboxToFOLDER(imfMboxFlags);
ahfo.ffFlagRemove = DwConvertIMAPMboxToFOLDER(IMAP_MBOX_ALLFLAGS) & ~(ahfo.ffFlagAdd);
ahfo.ffFlagRemove |= FOLDER_NONEXISTENT; // Always remove: folder must exist if we listed it
ahfo.ffFlagRemove |= FOLDER_HIDDEN; // If we listed the folder, we no longer need to hide it
ahfo.ffFlagRemove |= FOLDER_CREATEONDEMAND; // If we listed the folder, we no longer need to create it
// Figure out which flags to add and remove
if (ISFLAGSET(dwAFTCFlags, AFTC_SUBSCRIBED) || FOLDER_INBOX == sfType)
ahfo.ffFlagAdd |= FOLDER_SUBSCRIBED; // This folder is subscribed
else if (ISFLAGSET(dwAFTCFlags, AFTC_NOTSUBSCRIBED))
ahfo.ffFlagRemove |= FOLDER_SUBSCRIBED; // This folder is no longer subscribed
if (AFTC_NOTRANSLATION & dwAFTCFlags)
ahfo.ffFlagAdd |= FOLDER_NOTRANSLATEUTF7;
else
ahfo.ffFlagRemove |= FOLDER_NOTRANSLATEUTF7;
if (IMAP_MBOX_NOINFERIORS & imfMboxFlags)
// NoInferiors folders cannot have children, so we never have to ask
ahfo.ffFlagAdd |= FOLDER_CHILDRENKNOWN;
else if (ISFLAGCLEAR(dwAFTCFlags, AFTC_KEEPCHILDRENKNOWN))
// Remove FOLDER_CHILDRENKNOWN from this fldr so we ask for its chldrn when it's expanded
ahfo.ffFlagRemove |= FOLDER_CHILDRENKNOWN;
hr = FindHierarchicalFolderName(pszMailboxName, cHierarchyChar,
pFolderID, &ahfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
AssertSz(FOLDERID_INVALID != *pFolderID, "Hey, what does it take to get a folder handle?");
exit:
return hr;
}
//***************************************************************************
// Function: RemovePrefixFromPath
//
// Purpose:
// This function removes the prefix from the given mailbox path. If the
// given path is a special folder, this function removes all of the special
// folder path prefix except for the leaf node (eg, "foo/Sent Items/bar"
// becomes "Sent Items/bar").
//
// Arguments:
// LPSTR pszPrefix [in] - the prefix to strip from pszMailboxName. Note
// that this may not necessarily be the prefix stripped from pszMailboxName,
// if we match a special folder path.
// LPSTR pszMailboxName [in] - the full path to the mailbox, incl prefix
// char cHierarchyChar [in] - used to interpret pszMailboxName.
// LPBOOL pfValidPrefix [out] - returns TRUE if this mailbox name has a
// valid prefix, FALSE otherwise. Pass NULL if not interested.
// SPECIALFOLDER *psfType [out] - returns SPECIALFOLDER (eg, FOLDER_NOTSPECIAL,
// FOLDER_INBOX) of folder. Pass NULL if not interested.
//
// Returns:
// LPSTR pointing past the prefix and hierarchy character.
//***************************************************************************
LPSTR CIMAPSync::RemovePrefixFromPath(LPSTR pszPrefix, LPSTR pszMailboxName,
char cHierarchyChar, LPBOOL pfValidPrefix,
SPECIALFOLDER *psfType)
{
LPSTR pszSpecial = NULL;
LPSTR pszRFP = NULL;
BOOL fValidPrefix = FALSE;
SPECIALFOLDER sfType;
TraceCall("CIMAPSync::RemovePrefixFromPath");
IxpAssert(INVALID_HIERARCHY_CHAR != cHierarchyChar);
// Check for special folder path prefixes
pszSpecial = ImapUtil_GetSpecialFolderType(m_pszAccountID, pszMailboxName,
cHierarchyChar, pszPrefix, &sfType);
if (NULL != pszSpecial)
fValidPrefix = TRUE;
// If this is a special folder, no need to check for root folder prefix
if (FOLDER_NOTSPECIAL != sfType)
{
IxpAssert(NULL != pszSpecial);
pszMailboxName = pszSpecial;
goto exit;
}
// Check for the root folder prefix
if ('\0' != pszPrefix[0] && '\0' != cHierarchyChar)
{
int iResult, iPrefixLength;
// Do case-INSENSITIVE compare (IE5 bug #59121). If we ask for Inbox/* we must be
// able to handle receipt of INBOX/*. Don't worry about case-sensitive servers since
// they will never return an RFP of different case than the one we specified
iPrefixLength = lstrlen(pszPrefix);
iResult = StrCmpNI(pszMailboxName, pszPrefix, iPrefixLength);
if (0 == iResult)
{
// Prefix name found at front of this mailbox name! Remove it iff
// it is followed immediately by hierarchy character
if (cHierarchyChar == pszMailboxName[iPrefixLength])
{
pszRFP = pszMailboxName + iPrefixLength + 1; // Point past the hierarchy char
fValidPrefix = TRUE;
}
else if ('\0' == pszMailboxName[iPrefixLength])
{
pszRFP = pszMailboxName + iPrefixLength;
fValidPrefix = TRUE;
}
}
}
else
fValidPrefix = TRUE;
// We basically want to return the shortest mailbox name. For instance, in choosing
// between "INBOX.foo" and "foo", we should choose "foo"
IxpAssert(pszMailboxName > NULL && pszRFP >= NULL && pszSpecial >= NULL);
if (NULL != pszRFP || NULL != pszSpecial)
{
IxpAssert(pszRFP >= pszMailboxName || pszSpecial >= pszMailboxName);
pszMailboxName = max(pszRFP, pszSpecial);
}
exit:
if (NULL != pfValidPrefix)
*pfValidPrefix = fValidPrefix;
if (NULL != psfType)
*psfType = sfType;
return pszMailboxName;
}
//***************************************************************************
// Function: FindHierarchicalFolderName
//
// Purpose:
// This function takes a mailbox name as returned by LIST/LSUB and
// determines whether the given mailbox already exists in the folder cache.
// If so, a handle to the folder is returned. If not, and the fCreate argument
// is TRUE, then the mailbox and any intermediate nodes are created, and a
// handle to the mailbox (leaf node) is returned.
//
// Arguments:
// LPSTR lpszFolderPath [in] - the name of the mailbox as returned by
// a LIST or LSUB response. This should NOT include the prefix!
// char cHierarchyChar [in] - the hierarchy character used in
// lpszFolderPath. Used to determine parenthood.
// FOLDERID *pidTarget [out] - if the function is successful, a handle
// to the folder is returned here.
// ADD_HIER_FLDR_OPTIONS pahfoCreateInfo [in] - set to NULL if this function
// should find the given lpszFolderPath, but NOT create the folder. Pass
// in a ptr to a ADD_HIER_FLDR_OPTIONS structure if the folder should be
// created. pahfoCreateInfo defines the dwImapFlags and sftype to use
// if the folder has to be created.
//
// Returns:
// HRESULT indicating success or failure. If successful, a handle to the
// desired folder is returned in the pidTarget parameter. There are two
// possible success results:
// S_OK - found the folder, did not have to create
// S_CREATED - folder was successfully created
//***************************************************************************
HRESULT CIMAPSync::FindHierarchicalFolderName(LPSTR lpszFolderPath,
char cHierarchyChar,
FOLDERID *pidTarget,
ADD_HIER_FLDR_OPTIONS *pahfoCreateInfo)
{
char *pszCurrentFldrName;
FOLDERID idCurrent, idPrev;
HRESULT hr;
LPSTR pszTok;
LPSTR pszIHateStrTok = NULL;
char szHierarchyChar[2];
TraceCall("CIMAPSync::FindHierarchicalFolderName");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != lpszFolderPath);
IxpAssert(NULL != pidTarget);
// Initialize variables
*pidTarget = FOLDERID_INVALID;
hr = S_OK;
idPrev = FOLDERID_INVALID;
idCurrent = m_idIMAPServer;
szHierarchyChar[0] = cHierarchyChar;
szHierarchyChar[1] = '\0';
#ifdef DEBUG
// Make sure this fn is never called with a prefix (DEBUG-ONLY)
// Note that false alarm is possible, eg, RFP=foo and folder="foo/foo/bar".
BOOL fValidPrefix;
LPSTR pszPostPrefix;
pszPostPrefix = RemovePrefixFromPath(m_szRootFolderPrefix, lpszFolderPath,
cHierarchyChar, &fValidPrefix, NULL);
AssertSz(FALSE == fValidPrefix || pszPostPrefix == lpszFolderPath,
"Make sure you've removed the prefix before calling this fn!");
#endif // DEBUG
// Initialize pszCurrentFldrName to point to name of first-level mailbox node
// $REVIEW: We now need to remove the reference portion of the LIST/LSUB cmd
// from the mailbox name!
pszIHateStrTok = StringDup(lpszFolderPath);
pszTok = pszIHateStrTok;
pszCurrentFldrName = StrTokEx(&pszTok, szHierarchyChar);
// Loop through mailbox node names until we hit the leaf node
while (NULL != pszCurrentFldrName)
{
LPSTR pszNextFldrName;
// Pre-load the NEXT folder node so we know when we are at the leaf node
pszNextFldrName = StrTokEx(&pszTok, szHierarchyChar);
// Look for the current folder name
idPrev = idCurrent;
hr = GetFolderIdFromName(m_pStore, pszCurrentFldrName, idCurrent, &idCurrent);
IxpAssert(SUCCEEDED(hr) || FOLDERID_INVALID == idCurrent);
if (NULL == pahfoCreateInfo)
{
if (FOLDERID_INVALID == idCurrent)
break; // Fldr doesn't exist and user doesn't want to create it
}
else
{
// Create desired folder, including intermediate nodes
hr = CreateFolderNode(idPrev, &idCurrent, pszCurrentFldrName,
pszNextFldrName, cHierarchyChar, pahfoCreateInfo);
if (FAILED(hr))
break;
}
// Advance to the next folder node name
pszCurrentFldrName = pszNextFldrName;
}
// Return results
if (SUCCEEDED(hr) && FOLDERID_INVALID != idCurrent)
{
*pidTarget = idCurrent;
}
else
{
IxpAssert(FOLDERID_INVALID == *pidTarget); // We set this at start of fn
if (SUCCEEDED(hr))
hr = DB_E_NOTFOUND; // Can't return success
}
SafeMemFree(pszIHateStrTok);
return hr;
}
//***************************************************************************
// Function: CreateFolderNode
//
// Purpose:
// This function is called when creating a new folder in the foldercache.
// It is called for every node from the root folder and the new folder.
// This function is responsible for creating the terminal node and any
// intermediate nodes. If these nodes already exist, this function is
// responsible for adjusting the FLDR_* flags to reflect the new folder
// that is about to be added.
//
// Arguments:
// FOLDERID idPrev [in] - FOLDERID to parent of current node.
// FOLDERID *pidCurrent [in/out] - FOLDERID to current node. If current node
// exists, this is a valid FOLDERID. If the current node must be created,
// the value here is FOLDERID_INVALID. In this case, the FOLDERID of the
// created node is returned here.
// LPSTR pszCurrentFldrName [in] - the name of the current folder node.
// LPSTR pszNextFldrName [in] - the name of the next folder node. This is
// NULL if the current node is the terminal node.
// char cHierarchyChar [in] - hierarchy character for this folder path.
// Used to save FLDINFO::bHierarchy.
// ADD_HIER_FLDR_OPTIONS *pahfoCreateInfo [in] - information used to create
// the terminal folder node and update all of its parent nodes that
// already exist.
//
// Returns:
// HRESULT indicating success or failure. S_CREATED means a folder node
// was created.
//***************************************************************************
HRESULT CIMAPSync::CreateFolderNode(FOLDERID idPrev, FOLDERID *pidCurrent,
LPSTR pszCurrentFldrName,
LPSTR pszNextFldrName, char cHierarchyChar,
ADD_HIER_FLDR_OPTIONS *pahfoCreateInfo)
{
HRESULT hr = S_OK;
FOLDERINFO fiFolderInfo;
BOOL fFreeInfo = FALSE;
TraceCall("CIMAPSync::CreateFolderNode");
IxpAssert(NULL != pahfoCreateInfo);
IxpAssert(0 == (pahfoCreateInfo->ffFlagAdd & pahfoCreateInfo->ffFlagRemove));
// If current folder name not found, we have to create it
if (FOLDERID_INVALID == *pidCurrent)
{
// Initialize
ZeroMemory(&fiFolderInfo, sizeof(fiFolderInfo));
// FIRST: Add folder to folder cache
// Fill out a folderinfo structure (just use it as a scratchpad)
fiFolderInfo.idParent = idPrev;
fiFolderInfo.pszName = pszCurrentFldrName;
fiFolderInfo.bHierarchy = cHierarchyChar;
// If this is the last folder node name (ie, leaf node), use the
// IMAP flags returned via the LIST/LSUB, and use the supplied
// special folder type
if (NULL == pszNextFldrName)
{
fiFolderInfo.tySpecial = pahfoCreateInfo->sfType;
fiFolderInfo.dwFlags |= pahfoCreateInfo->ffFlagAdd;
fiFolderInfo.dwFlags &= ~(pahfoCreateInfo->ffFlagRemove);
if (fiFolderInfo.tySpecial == FOLDER_INBOX)
fiFolderInfo.dwFlags |= FOLDER_DOWNLOADALL;
}
else
{
// Otherwise, here are the defaults
// Non-listed folders are \NoSelect by default, and candidates for deletion
fiFolderInfo.dwFlags = FOLDER_NOSELECT | FOLDER_NONEXISTENT;
fiFolderInfo.tySpecial = FOLDER_NOTSPECIAL;
}
// Add folder to folder cache
hr = m_pStore->CreateFolder(NOFLAGS, &fiFolderInfo, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
*pidCurrent = fiFolderInfo.idFolder;
hr = S_CREATED; // Tell the user we created this folder
}
else if (NULL == pszNextFldrName)
{
DWORD dwFlagsChanged = 0;
BOOL fChanged = FALSE;
// Folder exists, check that its flags are correct
hr = m_pStore->GetFolderInfo(*pidCurrent, &fiFolderInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
fFreeInfo = TRUE;
if (fiFolderInfo.bHierarchy != cHierarchyChar)
{
AssertSz(INVALID_HIERARCHY_CHAR == (char) fiFolderInfo.bHierarchy, "What's YOUR excuse?");
fiFolderInfo.bHierarchy = cHierarchyChar;
fChanged = TRUE;
}
if (NULL == pszNextFldrName && (fiFolderInfo.tySpecial != pahfoCreateInfo->sfType ||
(fiFolderInfo.dwFlags & pahfoCreateInfo->ffFlagAdd) != pahfoCreateInfo->ffFlagAdd ||
(fiFolderInfo.dwFlags & pahfoCreateInfo->ffFlagRemove) != 0))
{
DWORD dwFlagAddChange;
DWORD dwFlagRemoveChange;
// The terminal folder node exists, set everything given via pahfoCreateInfo
// Check if anything changed, first
if (pahfoCreateInfo->sfType == FOLDER_INBOX &&
fiFolderInfo.tySpecial != pahfoCreateInfo->sfType)
fiFolderInfo.dwFlags |= FOLDER_DOWNLOADALL;
fiFolderInfo.tySpecial = pahfoCreateInfo->sfType;
// Figure out which flags changed so we know if we need to recalculate parents
dwFlagAddChange = (fiFolderInfo.dwFlags & pahfoCreateInfo->ffFlagAdd) ^
pahfoCreateInfo->ffFlagAdd;
dwFlagRemoveChange = (~(fiFolderInfo.dwFlags) & pahfoCreateInfo->ffFlagRemove) ^
pahfoCreateInfo->ffFlagRemove;
dwFlagsChanged = dwFlagAddChange | dwFlagRemoveChange;
fiFolderInfo.dwFlags |= pahfoCreateInfo->ffFlagAdd;
fiFolderInfo.dwFlags &= ~(pahfoCreateInfo->ffFlagRemove);
fChanged = TRUE;
}
// Set the folder properties
if (fChanged)
{
hr = m_pStore->UpdateRecord(&fiFolderInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
}
exit:
if (fFreeInfo)
m_pStore->FreeRecord(&fiFolderInfo);
return hr;
}
//***************************************************************************
// Function: SetTranslationMode
//
// Purpose:
// This function enables or disables mailbox translation in IIMAPTransport2,
// depending on whether the FOLDER_NOTRANSLATEUTF7 flags is set for this folder.
//
// Returns:
// HRESULT indicating success or failure. Success codes include:
// S_OK - mailbox translation has been successfully enabled.
// S_FALSE - mailbox translation has been successfully disabled.
//***************************************************************************
HRESULT CIMAPSync::SetTranslationMode(FOLDERID idFolderID)
{
HRESULT hrResult = S_OK;
FOLDERINFO fiFolderInfo = {0};
DWORD dwTranslateFlags;
BOOL fTranslate = TRUE;
BOOL fFreeInfo = FALSE;
TraceCall("CIMAPSync::SetTranslationMode");
// Check for FOLDERID_INVALID (we get this during folder lists)
// If FOLDERID_INVALID, assume we want to translate everything: leave fiFolderInfo at zero
if (FOLDERID_INVALID != idFolderID)
{
hrResult = m_pStore->GetFolderInfo(idFolderID, &fiFolderInfo);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
fFreeInfo = TRUE;
}
else
{
Assert(0 == fiFolderInfo.dwFlags);
}
fTranslate = TRUE;
dwTranslateFlags = IMAP_MBOXXLATE_DEFAULT | IMAP_MBOXXLATE_VERBATIMOK | IMAP_MBOXXLATE_RETAINCP;
if (fiFolderInfo.dwFlags & FOLDER_NOTRANSLATEUTF7)
{
fTranslate = FALSE;
dwTranslateFlags |= IMAP_MBOXXLATE_DISABLE;
dwTranslateFlags &= ~(IMAP_MBOXXLATE_DEFAULT);
}
hrResult = m_pTransport->SetDefaultCP(dwTranslateFlags, 0);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
exit:
if (fFreeInfo)
m_pStore->FreeRecord(&fiFolderInfo);
if (SUCCEEDED(hrResult))
hrResult = (fTranslate ? S_OK : S_FALSE);
return hrResult;
} // SetTranslationMode
//***************************************************************************
//***************************************************************************
BOOL CIMAPSync::isUSASCIIOnly(LPCSTR pszFolderName)
{
LPCSTR psz;
BOOL fUSASCII = TRUE;
psz = pszFolderName;
while ('\0' != *psz)
{
if (0 != (*psz & 0x80))
{
fUSASCII = FALSE;
break;
}
psz += 1;
}
return fUSASCII;
} // isUSASCIIOnly
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::CheckFolderNameValidity(LPCSTR pszName)
{
HRESULT hrResult = S_OK;
if (NULL == pszName || '\0' == *pszName)
{
hrResult = TraceResult(E_INVALIDARG);
goto exit;
}
// Figure out what our root hierarchy character is: assume that server does not
// support multiple hierarchy characters
if (INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar)
{
hrResult = LoadSaveRootHierarchyChar(fLOAD_HC);
if (FAILED(hrResult))
{
TraceResult(hrResult);
hrResult = S_OK; // We can't say if this is valid or not, so just assume it is
goto exit;
}
}
if ('\0' == m_cRootHierarchyChar || INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar)
goto exit; // Anything goes!
while ('\0' != *pszName)
{
// No hierarchy characters are allowed in the folder name, except at the very end
if (m_cRootHierarchyChar == *pszName && '\0' != *(pszName + 1))
{
// Figure out which HRESULT to use (we need to bring up the correct text)
switch (m_cRootHierarchyChar)
{
case '/':
hrResult = STORE_E_IMAP_HC_NOSLASH;
break;
case '\\':
hrResult = STORE_E_IMAP_HC_NOBACKSLASH;
break;
case '.':
hrResult = STORE_E_IMAP_HC_NODOT;
break;
default:
hrResult = STORE_E_IMAP_HC_NOHC;
break;
}
TraceResult(hrResult);
goto exit;
}
// Advance pointer
pszName += 1;
}
exit:
return hrResult;
}
//***************************************************************************
// Function: RenameFolderHelper
//
// Purpose:
// This function is called by RenameFolder. This function is responsible
// for issuing the RENAME command for the folder which is to be renamed.
// If the folder to be renamed does not actually exist (eg, Cyrus server),
// this function recurses on the child folders until a real folder is found.
//
// Arguments:
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::RenameFolderHelper(FOLDERID idFolder,
LPSTR pszFolderPath,
char cHierarchyChar,
LPSTR pszNewFolderPath)
{
HRESULT hr;
CRenameFolderInfo *pRenameInfo = NULL;
FOLDERINFO fiFolderInfo;
IEnumerateFolders *pFldrEnum = NULL;
BOOL fFreeInfo = FALSE;
TraceCall("CIMAPSync::RenameFolderHelper");
IxpAssert(m_cRef > 0);
// Check if the folder actually exists on the IMAP server
hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// If current folder doesn't exist, recurse rename cmd on child folders
fFreeInfo = TRUE;
if (fiFolderInfo.dwFlags & FOLDER_NONEXISTENT) {
FOLDERINFO fiChildFldrInfo;
// Perform rename on folder nodes which EXIST: recurse through children
hr = m_pStore->EnumChildren(idFolder, fUNSUBSCRIBE, &pFldrEnum);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
hr = pFldrEnum->Next(1, &fiChildFldrInfo, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
while (S_OK == hr)
{
LPSTR pszOldPath, pszNewPath;
DWORD dwLeafFolderLen, dwFolderPathLen, dwNewFolderPathLen;
DWORD cchOldPath, cchNewPath;
BOOL fResult;
CHAR szHierarchyStr[2];
szHierarchyStr[0] = cHierarchyChar;
szHierarchyStr[1] = 0;
// Calculate string sizes, + 2 for HC and null-term
dwLeafFolderLen = lstrlen(fiChildFldrInfo.pszName);
dwFolderPathLen = lstrlen(pszFolderPath);
dwNewFolderPathLen = lstrlen(pszNewFolderPath);
// Allocate space
cchOldPath = dwFolderPathLen + dwLeafFolderLen + 2;
fResult = MemAlloc((void **)&pszOldPath, cchOldPath * sizeof(pszOldPath[0]));
if (FALSE == fResult)
{
m_pStore->FreeRecord(&fiChildFldrInfo);
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
cchNewPath = dwNewFolderPathLen + dwLeafFolderLen + 2;
fResult = MemAlloc((void **)&pszNewPath, cchNewPath * sizeof(pszNewPath[0]));
if (FALSE == fResult)
{
MemFree(pszOldPath);
m_pStore->FreeRecord(&fiChildFldrInfo);
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
// Append current child's name to current path, new path
StrCpyN(pszOldPath, pszFolderPath, cchOldPath);
StrCatBuff(pszOldPath, szHierarchyStr, cchOldPath);
StrCatBuff(pszOldPath, fiChildFldrInfo.pszName, cchOldPath);
StrCpyN(pszNewPath, pszNewFolderPath, cchNewPath);
StrCatBuff(pszNewPath, szHierarchyStr, cchNewPath);
StrCatBuff(pszNewPath, fiChildFldrInfo.pszName, cchNewPath);
// Recurse into the children, in hopes of finding an existing folder
hr = RenameFolderHelper(fiChildFldrInfo.idFolder, pszOldPath, cHierarchyChar, pszNewPath);
MemFree(pszOldPath);
MemFree(pszNewPath);
if (FAILED(hr))
{
m_pStore->FreeRecord(&fiChildFldrInfo);
TraceResult(hr);
goto exit;
}
// Load in the next child folder
m_pStore->FreeRecord(&fiChildFldrInfo);
hr = pFldrEnum->Next(1, &fiChildFldrInfo, NULL);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
} // while (S_OK == hr)
goto exit; // We don't attempt to rename non-existent folders
} // if (fiFolderInfo.dwImapFlags & FOLDER_NONEXISTENT)
// Create a CRenameFolderInfo structure
pRenameInfo = new CRenameFolderInfo;
if (NULL == pRenameInfo)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
// Fill in all the fields
pRenameInfo->pszFullFolderPath = StringDup(pszFolderPath);
pRenameInfo->cHierarchyChar = cHierarchyChar;
pRenameInfo->pszNewFolderPath = StringDup(pszNewFolderPath);
pRenameInfo->idRenameFolder = idFolder;
// Send the RENAME command
pRenameInfo->pszRenameCmdOldFldrPath = StringDup(pszFolderPath);
hr = _EnqueueOperation(tidRENAME, (LPARAM)pRenameInfo, icRENAME_COMMAND,
pRenameInfo->pszNewFolderPath, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
exit:
if (fFreeInfo)
m_pStore->FreeRecord(&fiFolderInfo);
if (NULL != pRenameInfo)
pRenameInfo->Release();
if (NULL != pFldrEnum)
pFldrEnum->Release();
return hr;
} // RenameFolderHelper
//***************************************************************************
// Function: RenameTreeTraversal
//
// Purpose:
// This function performs the requested operation on all child folders of
// the rename folder (specified in pRenameInfo->hfRenameFolder). For example,
// the tidRENAMESUBSCRIBE operation indicates that the entire renamed folder
// hierarchy should be subscribed.
//
// Arguments:
// WPARAM wpOperation [in] - identifies the operation to perform on the
// rename hierarchy. Current operations include:
// tidRENAMESUBSCRIBE - subscribe new (renamed) folder hierarchy
// tidRENAMESUBSCRIBE_AGAIN - same as tidRENAMESUBSCRIBE
// tidRENAMERENAME - issue individual RENAME's for all old child folders
// (simulates an atomic rename)
// tidRENAMELIST - list the FIRST child of the rename folder.
// tidRENAMEUNSUBSCRIBE - unsubscribe old folder hierarchy.
//
// CRenameFolderInfo [in] - the CRenameFolderInfo class associated with
// the RENAME operation.
// BOOL fIncludeRenameFolder [in] - TRUE if the rename folder (top node)
// should be included in the operation, otherwise FALSE.
//
// Returns:
// HRESULT indicating success or failure. S_FALSE is a possible result,
// indicating that recursion has occurred in RenameTreeTraversalHelper.
//***************************************************************************
HRESULT CIMAPSync::RenameTreeTraversal(WPARAM wpOperation,
CRenameFolderInfo *pRenameInfo,
BOOL fIncludeRenameFolder)
{
HRESULT hrResult;
LPSTR pszCurrentPath;
DWORD dwSizeOfCurrentPath;
FOLDERINFO fiFolderInfo;
BOOL fFreeInfo = FALSE;
TraceCall("CIMAPSync::RenameTreeTraversal");
IxpAssert(m_cRef > 0);
// Construct the path name to renamed folder's parent, based on operation
if (tidRENAMESUBSCRIBE == wpOperation ||
tidRENAMESUBSCRIBE_AGAIN == wpOperation ||
tidRENAMERENAME == wpOperation)
pszCurrentPath = pRenameInfo->pszNewFolderPath;
else
pszCurrentPath = pRenameInfo->pszFullFolderPath;
dwSizeOfCurrentPath = lstrlen(pszCurrentPath);
// We need to get some details about renamed folder node to start the recursion
hrResult = m_pStore->GetFolderInfo(pRenameInfo->idRenameFolder, &fiFolderInfo);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
fFreeInfo = TRUE;
// Start the mayhem
hrResult = RenameTreeTraversalHelper(wpOperation, pRenameInfo, pszCurrentPath,
dwSizeOfCurrentPath, fIncludeRenameFolder, &fiFolderInfo);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
exit:
if (fFreeInfo)
m_pStore->FreeRecord(&fiFolderInfo);
return hrResult;
} // RenameTreeTraversal
//***************************************************************************
// Function: RenameTreeTraversalHelper
//
// Purpose:
// This function actually does the work for RenameTreeTraversal. This
// function is separate so that it can perform the necessary recursion to
// execute the desired operation on every child folder of the rename folder.
//
// Arguments:
// WPARAM wpOperation [in] - same as for RenameTreeTraversal.
// CRenameFolderInfo [in/out] - same as for RenameTreeTraversal. Member
// variables of this class are updated as required in this function
// (for instance, iNumListRespExpected is incremented for each LIST sent).
// LPSTR pszCurrentFldrPath [in/out] - a string describing the full path to
// the current folder. The first call to this function (from Rename-
// TreeTraversal) is a full path to the rename folder. This function
// modifies this buffer (adds leaf node names) as needed.
// DWORD dwLengthOfCurrentPath [in] - length of pszCurrentFldrPath.
// BOOL fIncludeThisFolder [in] - TRUE if this function should perform
// the requested operation on the current node. Otherwise, FALSE.
// FOLDERINFO *pfiCurrentFldrInfo [in] - contains information about the
// current folder.
//
// Returns:
// HRESULT indicating success or failure. S_FALSE is a possible return
// result, typically indicating that recursion has taken place.
//***************************************************************************
HRESULT CIMAPSync::RenameTreeTraversalHelper(WPARAM wpOperation,
CRenameFolderInfo *pRenameInfo,
LPSTR pszCurrentFldrPath,
DWORD dwLengthOfCurrentPath,
BOOL fIncludeThisFolder,
FOLDERINFO *pfiCurrentFldrInfo)
{
HRESULT hrResult = S_OK;
FOLDERINFO fiFolderInfo;
IEnumerateFolders *pFldrEnum = NULL;
TraceCall("CIMAPSync::RenameTreeTraversalHelper");
IxpAssert(m_cRef > 0);
// Execute the requested operation, if current folder is not suppressed
// and if current folder actually exists
if (fIncludeThisFolder && 0 == (pfiCurrentFldrInfo->dwFlags & FOLDER_NONEXISTENT))
{
switch (wpOperation)
{
case tidRENAMESUBSCRIBE:
case tidRENAMESUBSCRIBE_AGAIN:
hrResult = _EnqueueOperation(wpOperation, (LPARAM) pRenameInfo,
icSUBSCRIBE_COMMAND, pszCurrentFldrPath, uiNORMAL_PRIORITY);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
pRenameInfo->iNumSubscribeRespExpected += 1;
break; // case tidRENAMESUBSCRIBE
case tidRENAMELIST:
// This operation is special-cased to send only ONE list command, a list cmd
// for the first child fldr. The reason this operation is HERE is because this
// operation used to list ALL the child fldrs, until I found that IIMAPTransport
// couldn't resolve the ambiguities. (IIMAPTransport will eventually get queuing).
IxpAssert(0 == pRenameInfo->iNumListRespExpected); // Send only ONE list cmd!
hrResult = _EnqueueOperation(tidRENAMELIST, (LPARAM) pRenameInfo,
icLIST_COMMAND, pszCurrentFldrPath, uiNORMAL_PRIORITY);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
pRenameInfo->iNumListRespExpected += 1;
goto exit; // Do not recurse any further into the folder hierarchy
break; // case tidRENAMELIST
case tidRENAMEUNSUBSCRIBE:
hrResult = _EnqueueOperation(tidRENAMEUNSUBSCRIBE, (LPARAM) pRenameInfo,
icUNSUBSCRIBE_COMMAND, pszCurrentFldrPath, uiNORMAL_PRIORITY);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
pRenameInfo->iNumUnsubscribeRespExpected += 1;
break; // case tidRENAMEUNSUBSCRIBE
case tidRENAMERENAME: {
LPSTR pszRenameCmdOldFldrPath;
DWORD cchFullFolderPathLen, cchLeafNodeLen;
LPSTR pszOldFldrPath;
BOOL fResult;
// Allocate a buffer for old folder path
cchFullFolderPathLen = lstrlen(pRenameInfo->pszFullFolderPath);
cchLeafNodeLen = lstrlen(RemovePrefixFromPath(
pRenameInfo->pszNewFolderPath, pszCurrentFldrPath,
pRenameInfo->cHierarchyChar, NULL, NULL));
DWORD cchSizeOldFldrPath = (cchFullFolderPathLen + cchLeafNodeLen + 2);
fResult = MemAlloc((void **)&pszOldFldrPath, cchSizeOldFldrPath * sizeof(pszOldFldrPath[0]));
if (FALSE == fResult)
{
hrResult = TraceResult(E_OUTOFMEMORY); // Abort, folder paths aren't getting shorter
goto exit;
}
// Construct old folder path (MUST be below rename folder level)
MemFree(pRenameInfo->pszRenameCmdOldFldrPath);
StrCpyN(pszOldFldrPath, pRenameInfo->pszFullFolderPath, cchSizeOldFldrPath);
*(pszOldFldrPath + cchFullFolderPathLen) = pfiCurrentFldrInfo->bHierarchy;
StrCatBuff(pszOldFldrPath,
RemovePrefixFromPath(pRenameInfo->pszNewFolderPath, pszCurrentFldrPath, pRenameInfo->cHierarchyChar, NULL, NULL), cchSizeOldFldrPath);
pRenameInfo->pszRenameCmdOldFldrPath = pszOldFldrPath;
hrResult = _EnqueueOperation(tidRENAMERENAME, (LPARAM) pRenameInfo,
icRENAME_COMMAND, pszCurrentFldrPath, uiNORMAL_PRIORITY);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
pRenameInfo->iNumRenameRespExpected += 1;
} // case todRENAMERENAME
break; // case tidRENAMERENAME
default:
AssertSz(FALSE, "I don't know how to perform this operation.");
hrResult = TraceResult(E_FAIL);
goto exit;
} // switch (wpOperation)
} // if (fIncludeThisFolder)
// Now, recurse upon all my children, if there are any
if (0 == (FOLDER_HASCHILDREN & pfiCurrentFldrInfo->dwFlags))
goto exit; // We're done!
// Initialize the child-traversal-loop
hrResult = m_pStore->EnumChildren(pfiCurrentFldrInfo->idFolder, fUNSUBSCRIBE, &pFldrEnum);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
hrResult = pFldrEnum->Next(1, &fiFolderInfo, NULL);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
while (S_OK == hrResult)
{
LPSTR pszCurrentChild;
DWORD cchLengthOfCurrentChild;
BOOL fResult;
// Construct path to current child
cchLengthOfCurrentChild = dwLengthOfCurrentPath +
lstrlen(fiFolderInfo.pszName) + 1; // HC = 1
fResult = MemAlloc((void **)&pszCurrentChild, (cchLengthOfCurrentChild + 1) * sizeof(pszCurrentChild[0])); // 1 for null-term
if (FALSE == fResult)
{
m_pStore->FreeRecord(&fiFolderInfo);
hrResult = TraceResult(E_OUTOFMEMORY);
goto exit;
}
StrCpyN(pszCurrentChild, pszCurrentFldrPath, cchLengthOfCurrentChild+1);
*(pszCurrentChild + dwLengthOfCurrentPath) = pfiCurrentFldrInfo->bHierarchy;
StrCatBuff(pszCurrentChild, fiFolderInfo.pszName, cchLengthOfCurrentChild+1);
// Recurse on the child folder, NEVER suppress folders from here on in
hrResult = RenameTreeTraversalHelper(wpOperation, pRenameInfo,
pszCurrentChild, cchLengthOfCurrentChild, TRUE, &fiFolderInfo);
MemFree(pszCurrentChild);
if (FAILED(hrResult))
{
m_pStore->FreeRecord(&fiFolderInfo);
TraceResult(hrResult);
goto exit;
}
m_pStore->FreeRecord(&fiFolderInfo);
if (tidRENAMELIST == wpOperation)
break; // Special case for LIST: only send ONE list cmd (for first child fldr)
// Advance the loop
hrResult = pFldrEnum->Next(1, &fiFolderInfo, NULL);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
} // while
exit:
if (NULL != pFldrEnum)
pFldrEnum->Release();
return hrResult;
} // RenameTreeTraversalHelper
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::SubscribeSubtree(FOLDERID idFolder, BOOL fSubscribe)
{
HRESULT hrResult;
IEnumerateFolders *pFldrEnum = NULL;
FOLDERINFO fiFolderInfo;
TraceCall("CIMAPSync::SubscribeSubtree");
IxpAssert(m_cRef > 0);
IxpAssert(FOLDERID_INVALID != idFolder);
// First subscribe the current node
hrResult = m_pStore->SubscribeToFolder(idFolder, fSubscribe, NOSTORECALLBACK);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
// Now work on the children
hrResult = m_pStore->EnumChildren(idFolder, fUNSUBSCRIBE, &pFldrEnum);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
hrResult = pFldrEnum->Next(1, &fiFolderInfo, NULL);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
while (S_OK == hrResult)
{
// Recurse into children
hrResult = SubscribeSubtree(fiFolderInfo.idFolder, fSubscribe);
TraceError(hrResult); // Record error but otherwise continue
// Advance to next child
m_pStore->FreeRecord(&fiFolderInfo);
hrResult = pFldrEnum->Next(1, &fiFolderInfo, NULL);
TraceError(hrResult);
}
exit:
if (NULL != pFldrEnum)
pFldrEnum->Release();
return hrResult;
}
//***************************************************************************
// Function: FindRootHierarchyChar
//
// Purpose:
// This function is called to analyze hierarchy character information
// collected in m_phcfHierarchyCharInfo, and take appropriate action based
// on the analysis (for example, try to find the hierarchy character using
// a different method if the current method failed). Currently there are 3
// methods of finding a hierarchy character. I call these Plan A, B and C.
// Plan A: Look for hierarchy char in folder hierarchy listing.
// Plan B: Issue LIST c_szEmpty c_szEmpty
// Plan C: Create a temp fldr (no HC's in name), list it, delete it
// Plan Z: Give up and default HC to NIL. This is still under debate.
//
// Arguments:
// BOOL fPlanA_Only [in] - TRUE if this function should execute plan A
// only, and not execute plans B, C or Z.
// LPARAM lParam [in] - lParam to use when issuing IMAP commands
//
// Returns:
// If a hierarchy character is found, it is placed in m_cRootHierarchyChar.
//***************************************************************************
void CIMAPSync::FindRootHierarchyChar(BOOL fPlanA_Only, LPARAM lParam)
{
HRESULT hr;
TraceCall("CIMAPSync::FindRootHierarchyChar");
IxpAssert(m_cRef > 0);
AssertSz(INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar,
"You want to find the root hierarchy char... but you ALREADY have one. Ah! Efficient.");
if (NULL == m_phcfHierarchyCharInfo)
{
AssertSz(FALSE, "What's the idea, starting a folder DL without a hierarchy char finder?");
return;
}
// Figure out what the hierarchy char is from the collected information
AnalyzeHierarchyCharInfo();
// If we haven't found the hierarchy character, launch plan B or C
if (INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar && FALSE == fPlanA_Only)
{
switch (m_phcfHierarchyCharInfo->hcfStage)
{
case hcfPLAN_A:
// Didn't find in folder hierarchy DL (Plan A). Plan "B" is to issue <LIST c_szEmpty c_szEmpty>
m_phcfHierarchyCharInfo->hcfStage = hcfPLAN_B;
hr = _EnqueueOperation(tidHIERARCHYCHAR_LIST_B, lParam, icLIST_COMMAND,
c_szEmpty, uiNORMAL_PRIORITY);
TraceError(hr);
break; // case hcfPLAN_A
case hcfPLAN_B:
{
// Didn't find in <LIST c_szEmpty c_szEmpty> (Plan B). Plan "C": attempt CREATE, LIST, DELETE
// There's no folders on the server, so very little chance of conflict
// $REVIEW: Localize fldr name when IMAP handles UTF-7. (idsIMAP_HCFTempFldr)
StrCpyN(m_phcfHierarchyCharInfo->szTempFldrName, "DeleteMe", ARRAYSIZE(m_phcfHierarchyCharInfo->szTempFldrName));
m_phcfHierarchyCharInfo->hcfStage = hcfPLAN_C;
hr = _EnqueueOperation(tidHIERARCHYCHAR_CREATE, lParam, icCREATE_COMMAND,
m_phcfHierarchyCharInfo->szTempFldrName, uiNORMAL_PRIORITY);
TraceError(hr);
}
break; // case hcfPLAN_B
default:
case hcfPLAN_C:
IxpAssert(hcfPLAN_C == m_phcfHierarchyCharInfo->hcfStage);
AssertSz(FALSE, "This server won't budge - I can't figure out hierarchy char");
// $REVIEW: Should I put up a message box informing user of situation? Will they understand?
// We'll just have to assume the hierarchy char is NIL
// $REVIEW: Is this a good idea? What else can I do about it?
m_cRootHierarchyChar = '\0';
break; // case hcfPLAN_C
}
}
// Finally, if we've found hierarchy character, or assumed a value in case
// hcfPLAN_C above, stop the search and save character to disk
if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
{
StopHierarchyCharSearch();
hr = LoadSaveRootHierarchyChar(fSAVE_HC);
TraceError(hr);
}
}
//***************************************************************************
// Function: AnalyzeHierarchyCharInfo
//
// Purpose:
// This function examines m_phcfHierarchyCharInfo and attempts to determine
// what the root hierarchy character is. The rules it uses are as follows:
// 1) If more than 1 Non-NIL, Non-"." (NNND), hierarchy char is indeterminate.
// 2) If one NNND-HC found, it is taken as HC. "." and NIL HC's are ignored.
// 3) If no NNND-HC's, but we saw a ".", then "." is HC.
// 4) If no NNND-HC's, no ".", but we saw a non-INBOX NIL, then NIL is HC.
//***************************************************************************
void CIMAPSync::AnalyzeHierarchyCharInfo(void)
{
int i;
int iNonNilNonDotCount;
BYTE *pbBitArray;
TraceCall("CIMAPSync::AnalyzeHierarchyCharInfo");
IxpAssert(m_cRef > 0);
// First, count the number of non-NIL, non-"." hierarchy chars encountered
iNonNilNonDotCount = 0;
pbBitArray = m_phcfHierarchyCharInfo->bHierarchyCharBitArray;
for (i = 0; i < sizeof(m_phcfHierarchyCharInfo->bHierarchyCharBitArray); i++)
{
if (0 != *pbBitArray)
{
BYTE bCurrentByte;
int j;
// Count the number of bits set in this byte
bCurrentByte = *pbBitArray;
IxpAssert(1 == sizeof(bCurrentByte)); // Must change code for > 1 byte at a time
for (j=0; j<8; j++)
{
if (bCurrentByte & 0x01)
{
iNonNilNonDotCount += 1;
m_cRootHierarchyChar = i*8 + j;
}
bCurrentByte >>= 1;
}
}
// Advance the pointer
pbBitArray += 1;
}
// Set the hierarchy character based on priority rules: '/' or '\', then '.', then NIL
if (iNonNilNonDotCount > 1)
{
m_cRootHierarchyChar = INVALID_HIERARCHY_CHAR; // Which one WAS it?
// Nuke all flags and start afresh
AssertSz(FALSE, "Hey, lookee here! More than one NNND-HC! How quaint.");
ZeroMemory(m_phcfHierarchyCharInfo->bHierarchyCharBitArray,
sizeof(m_phcfHierarchyCharInfo->bHierarchyCharBitArray));
m_phcfHierarchyCharInfo->fDotHierarchyCharSeen = FALSE;
m_phcfHierarchyCharInfo->fNonInboxNIL_Seen = FALSE;
}
else if (0 == iNonNilNonDotCount)
{
// Hmmm, looks like we didn't find anything non-'.' or non-NIL
IxpAssert(INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar); // Just paranoid
if (m_phcfHierarchyCharInfo->fDotHierarchyCharSeen)
m_cRootHierarchyChar = '.';
else if (m_phcfHierarchyCharInfo->fNonInboxNIL_Seen)
m_cRootHierarchyChar = '\0';
// If we reach this point and INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar,
// all flags must be 0, so no need to nuke as for iNonNilNonDotCount > 1 above
}
else
{
// We found a non-NIL, non-"." hierarchy char. This will take priority
// over any NIL or "." hierarchy chars we encountered. STILL, I want to
// know if we talk to a server who has both one NNND-HC and a "." HC.
IxpAssert(1 == iNonNilNonDotCount);
AssertSz(FALSE == m_phcfHierarchyCharInfo->fDotHierarchyCharSeen,
"Take a look at THIS! A server with one NNND-HC and a '.' HC.");
}
}
//***************************************************************************
// Function: StopHierarchyCharSearch
//
// Purpose:
// This function stops future hierarchy character searches by freeing
// the m_phcfHierarchyCharInfo struct.
//***************************************************************************
void CIMAPSync::StopHierarchyCharSearch(void)
{
TraceCall("CIMAPSync::StopHierararchyCharSearch");
IxpAssert(m_cRef > 0);
// Deallocate m_phcfHierarchyCharInfo
if (NULL != m_phcfHierarchyCharInfo)
{
delete m_phcfHierarchyCharInfo;
m_phcfHierarchyCharInfo = NULL;
}
else {
AssertSz(FALSE, "No search for a root-lvl hierarchy character is in progress.");
}
}
//***************************************************************************
// Function: LoadSaveRootHierarchyChar
//
// Arguments:
// BOOL fSaveHC [in] - TRUE if we should save m_cRootHierarchyChar to
// the root folder entry in the folder cache. FALSE to read
// m_cRootHierarchyChar from the root folder entry in foldercache.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::LoadSaveRootHierarchyChar(BOOL fSaveHC)
{
FOLDERINFO fiRootFldrInfo;
HRESULT hr;
FOLDERID idCurrFldr;
BOOL fFreeInfo = FALSE;
TraceCall("CIMAPSync::LoadSaveRootHierarchyChar");
IxpAssert(m_cRef > 0);
IxpAssert(m_pStore != NULL);
// First thing we have to do is load fiFolderInfo with IMAP server node
hr = m_pStore->GetFolderInfo(m_idIMAPServer, &fiRootFldrInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Now load or save m_cRootHierarchyChar as directed by user
fFreeInfo = TRUE;
if (fSaveHC)
{
// Save the hierarchy character to disk
fiRootFldrInfo.bHierarchy = m_cRootHierarchyChar;
hr = m_pStore->UpdateRecord(&fiRootFldrInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
else
{
// Load the hierarchy character
m_cRootHierarchyChar = fiRootFldrInfo.bHierarchy;
}
exit:
if (fFreeInfo)
m_pStore->FreeRecord(&fiRootFldrInfo);
return hr;
}
//***************************************************************************
// Function: CreateNextSpecialFolder
//
// Purpose:
// This function is called after the tidINBOXLIST operation. This function
// tries to create all IMAP special folders (Sent Items, Drafts, Deleted
// Items). If no more special folders need to be created, the
// post-tidINBOXLIST activities are executed (tidPREFIXLIST/tidBROWSESTART/
// tidFOLDERLIST).
//
// Arguments:
// CREATE_FOLDER_INFO *pcfiCreateInfo [in] - pointer to CREATE_FOLDER_INFO
// with properly set pcfiCreateInfo. This function will
// MemFree pcfiCreateInfo->pszFullFolderPath and delete pcfiCreateInfo
// when all special folders have been created.
// LPBOOL pfCompletion [out] - returns TRUE if we are done creating special
// folders.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::CreateNextSpecialFolder(CREATE_FOLDER_INFO *pcfiCreateInfo,
LPBOOL pfCompletion)
{
HRESULT hr = S_OK;
HRESULT hrTemp;
LPARAM lParam = pcfiCreateInfo->lParam;
char szSpecialFldrPath[2*MAX_PATH + 3]; // Leave room for HC, null-term and asterisk
BOOL fDone = FALSE;
BOOL fPostOp = FALSE;
BOOL fSuppressRelease = FALSE;
IXPSTATUS ixpCurrentStatus;
TraceCall("CIMAPSync::CreateNextSpecialFolder");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pcfiCreateInfo);
IxpAssert(FOLDER_NOTSPECIAL != pcfiCreateInfo->dwCurrentSfType);
IxpAssert(FOLDER_NOTSPECIAL != pcfiCreateInfo->dwFinalSfType);
IxpAssert(FOLDER_OUTBOX != pcfiCreateInfo->dwCurrentSfType);
IxpAssert(FOLDER_OUTBOX != pcfiCreateInfo->dwFinalSfType);
IxpAssert(pcfiCreateInfo->dwFinalSfType <= FOLDER_MAX);
IxpAssert(pcfiCreateInfo->dwCurrentSfType <= pcfiCreateInfo->dwFinalSfType);
szSpecialFldrPath[0] = '\0';
// If we're looking for root-lvl hierarchy char, maybe this listing will help
if (NULL != m_phcfHierarchyCharInfo)
FindRootHierarchyChar(fHCF_PLAN_A_ONLY, lParam);
hrTemp = LoadSaveRootHierarchyChar(fLOAD_HC);
TraceError(hrTemp);
// Get the next folder path if we're in CSF_NEXTFOLDER or CSF_INIT stage
while (CSF_NEXTFOLDER == pcfiCreateInfo->csfCurrentStage || CSF_INIT == pcfiCreateInfo->csfCurrentStage)
{
// If CSF_NEXTFOLDER, bump up current special folder type and check for done-ness
if (CSF_NEXTFOLDER == pcfiCreateInfo->csfCurrentStage)
{
pcfiCreateInfo->dwCurrentSfType += 1;
if (FOLDER_OUTBOX == pcfiCreateInfo->dwCurrentSfType)
pcfiCreateInfo->dwCurrentSfType += 1; // Skip Outbox
if (pcfiCreateInfo->dwCurrentSfType > pcfiCreateInfo->dwFinalSfType)
{
fDone = TRUE;
break;
}
}
hr = ImapUtil_SpecialFldrTypeToPath(m_pszAccountID,
(SPECIALFOLDER) pcfiCreateInfo->dwCurrentSfType, NULL, m_cRootHierarchyChar,
szSpecialFldrPath, ARRAYSIZE(szSpecialFldrPath));
if (SUCCEEDED(hr))
{
// Re-use current pcfiCreateInfo to launch next creation attempt
if (NULL != pcfiCreateInfo->pszFullFolderPath)
MemFree(pcfiCreateInfo->pszFullFolderPath);
pcfiCreateInfo->idFolder = FOLDERID_INVALID;
pcfiCreateInfo->pszFullFolderPath = StringDup(szSpecialFldrPath);
pcfiCreateInfo->dwFlags = 0;
pcfiCreateInfo->csfCurrentStage = CSF_LIST;
break; // We're ready to create some special folders!
}
else if (CSF_INIT == pcfiCreateInfo->csfCurrentStage)
{
// Need to exit now on ANY failure, to avoid infinite loop
fDone = TRUE;
break;
}
else if (STORE_E_NOREMOTESPECIALFLDR == hr)
{
// Suppress error: current special folder is disabled or not supported on IMAP
hr = S_OK;
}
else
{
TraceResult(hr); // Record but ignore unexpected error
}
} // while
// Check for termination condition
if (fDone)
goto exit;
// If we reach this point, we're ready to act on this special folder
switch (pcfiCreateInfo->csfCurrentStage)
{
case CSF_INIT:
// CSF_INIT should be resolved by loading a special fldr path and going to CSF_LIST!!
hr = TraceResult(E_UNEXPECTED);
break;
case CSF_LIST:
IxpAssert('\0' != szSpecialFldrPath[0]);
if (FOLDER_INBOX == pcfiCreateInfo->dwCurrentSfType)
{
// For INBOX ONLY: Issue LIST <specialfldr>* to get subchildren of folder (and folder itself)
StrCatBuff(szSpecialFldrPath, g_szAsterisk, ARRAYSIZE(szSpecialFldrPath)); // Append "*" to special folder name
}
pcfiCreateInfo->csfCurrentStage = CSF_LSUBCREATE;
hr = _EnqueueOperation(tidSPECIALFLDRLIST, (LPARAM) pcfiCreateInfo,
icLIST_COMMAND, szSpecialFldrPath, uiNORMAL_PRIORITY);
TraceError(hr);
break;
case CSF_LSUBCREATE:
// Check if the LIST operation returned the special folder path
if (CFI_RECEIVEDLISTING & pcfiCreateInfo->dwFlags)
{
LPSTR pszPath;
// Folder exists: Issue LSUB <specialfldr>* to get subscribed subchildren
IxpAssert(NULL != pcfiCreateInfo->pszFullFolderPath &&
'\0' != pcfiCreateInfo->pszFullFolderPath[0]);
if (FOLDER_INBOX == pcfiCreateInfo->dwCurrentSfType)
{
// For INBOX only: Append "*" to special folder name
wnsprintf(szSpecialFldrPath, ARRAYSIZE(szSpecialFldrPath), "%s*", pcfiCreateInfo->pszFullFolderPath);
pszPath = szSpecialFldrPath;
}
else
pszPath = pcfiCreateInfo->pszFullFolderPath;
pcfiCreateInfo->dwFlags = 0;
pcfiCreateInfo->csfCurrentStage = CSF_CHECKSUB;
hr = _EnqueueOperation(tidSPECIALFLDRLSUB, (LPARAM) pcfiCreateInfo,
icLSUB_COMMAND, pszPath, uiNORMAL_PRIORITY);
TraceError(hr);
}
else
{
// Folder does not appear to exist: better create it
pcfiCreateInfo->dwFlags = 0;
pcfiCreateInfo->csfCurrentStage = CSF_NEXTFOLDER;
hr = _EnqueueOperation(tidCREATE, (LPARAM)pcfiCreateInfo, icCREATE_COMMAND,
pcfiCreateInfo->pszFullFolderPath, uiNORMAL_PRIORITY);
TraceError(hr);
}
break;
case CSF_CHECKSUB:
// Check if the LSUB operation returned the special folder path
if (CFI_RECEIVEDLISTING & pcfiCreateInfo->dwFlags)
{
// Special folder is already subscribed, advance to next folder
IxpAssert(FALSE == fDone);
pcfiCreateInfo->csfCurrentStage = CSF_NEXTFOLDER;
hr = CreateNextSpecialFolder(pcfiCreateInfo, &fDone);
TraceError(hr);
// BEWARE: do not access pcfiCreateInfo past this point, might be GONE
fSuppressRelease = TRUE;
}
else
{
FOLDERID idTemp;
LPSTR pszLocalPath;
char szInbox[CCHMAX_STRINGRES];
SPECIALFOLDER sfType;
// Special folder not subscribed. Subscribe it!
// We need to convert full path to local path. Local path = folder name as it appears in our cache
pszLocalPath = ImapUtil_GetSpecialFolderType(m_pszAccountID,
pcfiCreateInfo->pszFullFolderPath, m_cRootHierarchyChar,
m_szRootFolderPrefix, &sfType);
if (FOLDER_INBOX == sfType)
{
// SPECIAL CASE: We need to replace INBOX with the localized name for INBOX
LoadString(g_hLocRes, idsInbox, szInbox, ARRAYSIZE(szInbox));
pszLocalPath = szInbox;
}
// Remove special folder from unsubscribed folder list (ignore error)
if (NULL != m_pListHash)
{
hr = m_pListHash->Find(pszLocalPath, fREMOVE, (void **) &idTemp);
IxpAssert(FAILED(hr) || idTemp == pcfiCreateInfo->idFolder);
}
// Use full path here (not local path)
pcfiCreateInfo->csfCurrentStage = CSF_NEXTFOLDER;
hr = _EnqueueOperation(tidSPECIALFLDRSUBSCRIBE, (LPARAM)pcfiCreateInfo,
icSUBSCRIBE_COMMAND, pcfiCreateInfo->pszFullFolderPath, uiNORMAL_PRIORITY);
TraceError(hr);
}
break;
default:
AssertSz(FALSE, "We are at an unknown stage!");
hr = TraceResult(E_FAIL);
break;
}
exit:
// At this point, do not access pcfiCreateInfo if fSuppressRelease is TRUE!!
if (FAILED(hr))
fDone = TRUE;
// Check if we are done and there are post-create operations to perform
if (FALSE == fSuppressRelease && PCO_NONE != pcfiCreateInfo->pcoNextOp)
{
IxpAssert(PCO_APPENDMSG == pcfiCreateInfo->pcoNextOp);
if (fDone && SUCCEEDED(hr))
{
hr = _EnqueueOperation(tidUPLOADMSG, pcfiCreateInfo->lParam, icAPPEND_COMMAND,
pcfiCreateInfo->pszFullFolderPath, uiNORMAL_PRIORITY);
TraceError(hr);
fPostOp = TRUE; // Returns *pfCompletion = FALSE but releases CREATE_FOLDER_INFO
}
else if (FAILED(hr))
{
APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) pcfiCreateInfo->lParam;
SafeMemFree(pAppendInfo->pszMsgFlags);
SafeRelease(pAppendInfo->lpstmMsg);
delete pAppendInfo;
}
}
if (fDone && FALSE == fSuppressRelease)
{
EndFolderList();
if (NULL != pcfiCreateInfo->pszFullFolderPath)
MemFree(pcfiCreateInfo->pszFullFolderPath);
delete pcfiCreateInfo;
}
if (NULL != pfCompletion)
*pfCompletion = (fDone && FALSE == fPostOp);
return hr;
}
// This is not the only place to start a folder list. For example,
// look at successful tidPREFIXLIST. Use this fn only where applicable.
HRESULT CIMAPSync::_StartFolderList(LPARAM lParam)
{
HRESULT hr = E_FAIL;
IImnAccount *pAcct;
TraceCall("CIMAPSync::_StartFolderList");
IxpAssert(g_pAcctMan);
IxpAssert(m_cRef > 0);
if (!g_pAcctMan)
return E_UNEXPECTED;
// If user started a folder list, we'll clear the AP_IMAP_DIRTY property
// The goal is not to pester the user with refresh folder list dialogs
hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_pszAccountID, &pAcct);
TraceError(hr);
if (SUCCEEDED(hr))
{
DWORD dwSrc;
hr = pAcct->GetPropDw(AP_IMAP_DIRTY, &dwSrc);
TraceError(hr);
if (SUCCEEDED(hr))
{
DWORD dwDest;
AssertSz(0 == (dwSrc & ~(IMAP_FLDRLIST_DIRTY | IMAP_OE4MIGRATE_DIRTY |
IMAP_SENTITEMS_DIRTY | IMAP_DRAFTS_DIRTY)), "Please update my dirty bits!");
// Clear these dirty bits since folder refresh solves all of these problems
dwDest = dwSrc & ~(IMAP_FLDRLIST_DIRTY | IMAP_OE4MIGRATE_DIRTY |
IMAP_SENTITEMS_DIRTY | IMAP_DRAFTS_DIRTY);
if (dwDest != dwSrc)
{
hr = pAcct->SetPropDw(AP_IMAP_DIRTY, dwDest);
TraceError(hr);
if (SUCCEEDED(hr))
{
hr = pAcct->SaveChanges();
TraceError(hr);
}
}
}
pAcct->Release();
}
// Find out what translation mode we should be in
hr = SetTranslationMode((FOLDERID) lParam);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Did user specify a root folder prefix?
if ('\0' != m_szRootFolderPrefix[0])
{
// User-specified prefix exists. Check if prefix exists on IMAP server
hr = _EnqueueOperation(tidPREFIXLIST, lParam, icLIST_COMMAND,
m_szRootFolderPrefix, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
else
{
// No root prefix folder, start folder refresh
hr = _EnqueueOperation(tidFOLDERLIST, lParam, icLIST_COMMAND,
g_szAsterisk, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
exit:
return hr;
}
//***************************************************************************
// Function: OnResponse
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::OnResponse(const IMAP_RESPONSE *pimr)
{
HRESULT hr=S_OK;
TraceCall("CIMAPSync::OnResponse");
AssertSingleThreaded;
switch (pimr->irtResponseType)
{
case irtERROR_NOTIFICATION:
AssertSz(FALSE, "Received IIMAPCallback(irtERROR_NOTIFICATION). Ignoring it.");
break;
case irtCOMMAND_COMPLETION:
hr = _OnCmdComplete(pimr->wParam, pimr->lParam,
pimr->hrResult, pimr->lpszResponseText);
break;
case irtSERVER_ALERT:
hr = _ShowUserInfo(MAKEINTRESOURCE(idsIMAPServerAlertTitle),
MAKEINTRESOURCE(idsIMAPServerAlertIntro),
pimr->lpszResponseText);
break;
case irtPARSE_ERROR:
// Do not display PARSE errors to user. These are really just WARNINGS
// and so no need to interrupt flow with these. Besides, UW IMAP puts up
// tonnes of these when you ask for ENVELOPE and it feels that the msgs are mal-formed
break;
case irtMAILBOX_UPDATE:
hr = _OnMailBoxUpdate(pimr->irdResponseData.pmcMsgCount);
break;
case irtDELETED_MSG:
hr = _OnMsgDeleted(pimr->irdResponseData.dwDeletedMsgSeqNum);
break;
case irtFETCH_BODY:
hr = _OnFetchBody(pimr->hrResult, pimr->irdResponseData.pFetchBodyPart);
break;
case irtUPDATE_MSG:
AssertSz(FALSE, "We should no longer get irtUPDATE_MSG, but the extended version instead");
break;
case irtUPDATE_MSG_EX:
hr = _OnUpdateMsg(pimr->wParam, pimr->hrResult, pimr->irdResponseData.pFetchResultsEx);
break;
case irtAPPLICABLE_FLAGS:
hr = _OnApplFlags(pimr->wParam,
pimr->irdResponseData.imfImapMessageFlags);
break;
case irtPERMANENT_FLAGS:
hr = _OnPermFlags(pimr->wParam,
pimr->irdResponseData.imfImapMessageFlags,
pimr->lpszResponseText);
break;
case irtUIDVALIDITY:
hr = _OnUIDValidity(pimr->wParam,
pimr->irdResponseData.dwUIDValidity,
pimr->lpszResponseText);
break;
case irtREADWRITE_STATUS:
hr = _OnReadWriteStatus(pimr->wParam,
pimr->irdResponseData.bReadWrite,
pimr->lpszResponseText);
break;
case irtTRYCREATE:
_OnTryCreate(pimr->wParam, pimr->lpszResponseText);
break;
case irtSEARCH:
hr = _OnSearchResponse(pimr->wParam,
pimr->irdResponseData.prlSearchResults);
break;
case irtMAILBOX_LISTING:
hr = _OnMailBoxList(pimr->wParam,
pimr->lParam,
pimr->irdResponseData.illrdMailboxListing.pszMailboxName,
pimr->irdResponseData.illrdMailboxListing.imfMboxFlags,
pimr->irdResponseData.illrdMailboxListing.cHierarchyChar,
IXP_S_IMAP_VERBATIM_MBOX == pimr->hrResult);
break;
case irtAPPEND_PROGRESS:
IxpAssert(tidUPLOADMSG == pimr->wParam);
hr = _OnAppendProgress(pimr->lParam,
pimr->irdResponseData.papAppendProgress->dwUploaded,
pimr->irdResponseData.papAppendProgress->dwTotal);
break;
case irtMAILBOX_STATUS:
hr = _OnStatusResponse(pimr->irdResponseData.pisrStatusResponse);
break;
default:
AssertSz(FALSE, "Received unknown IMAP response type via OnResponse");
break;
}
TraceError(hr);
return S_OK; // never fail the OnResponse
}
//***************************************************************************
// Function: OnTimeout
// Description: See imnxport.idl for details.
//***************************************************************************
HRESULT CIMAPSync::OnTimeout(DWORD *pdwTimeout, IInternetTransport *pTransport)
{
HRESULT hr;
AssertSingleThreaded;
TraceCall("CIMAPSync::OnTimeout");
IxpAssert(m_cRef > 0);
if (NULL == m_pCurrentCB)
return S_OK; // We'll just wait until the cows come home
else
return m_pCurrentCB->OnTimeout(&m_rInetServerInfo, pdwTimeout, IXP_IMAP);
}
//***************************************************************************
// Function: OnLogonPrompt
// Description: See imnxport.idl for details.
//***************************************************************************
HRESULT CIMAPSync::OnLogonPrompt(LPINETSERVER pInetServer,
IInternetTransport *pTransport)
{
BOOL bResult;
char szPassword[CCHMAX_PASSWORD];
HRESULT hr;
HWND hwnd;
IxpAssert(m_cRef > 0);
AssertSingleThreaded;
// Check if we have a cached password that's different from current password
hr = GetPassword(pInetServer->dwPort, pInetServer->szServerName, pInetServer->szUserName,
szPassword, ARRAYSIZE(szPassword));
if (SUCCEEDED(hr) && 0 != lstrcmp(szPassword, pInetServer->szPassword))
{
StrCpyN(pInetServer->szPassword, szPassword, ARRAYSIZE(pInetServer->szPassword));
return S_OK;
}
// Propagate call up to callback
if (NULL == m_pCurrentCB)
return S_FALSE;
hr = m_pCurrentCB->OnLogonPrompt(pInetServer, IXP_IMAP);
if (S_OK == hr)
{
// Cache password for future reference this session
SavePassword(pInetServer->dwPort, pInetServer->szServerName,
pInetServer->szUserName, pInetServer->szPassword);
}
else if (S_FALSE == hr)
{
m_hrOperationResult = STORE_E_OPERATION_CANCELED;
LoadString(g_hLocRes, IDS_IXP_E_USER_CANCEL, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
else if (FAILED(hr))
m_hrOperationResult = hr;
return hr;
}
//***************************************************************************
// Function: OnPrompt
// Description: See imnxport.idl for details.
//***************************************************************************
INT CIMAPSync::OnPrompt(HRESULT hrError, LPCTSTR pszText, LPCTSTR pszCaption,
UINT uType, IInternetTransport *pTransport)
{
INT iResult=IDCANCEL;
IxpAssert(m_cRef > 0);
AssertSingleThreaded;
if (NULL != m_pCurrentCB)
{
HRESULT hr;
hr = m_pCurrentCB->OnPrompt(hrError, pszText, pszCaption,
uType, &iResult);
TraceError(hr);
}
return iResult;
}
//***************************************************************************
// Function: OnStatus
// Description: See imnxport.idl for details.
//***************************************************************************
HRESULT CIMAPSync::OnStatus(IXPSTATUS ixpStatus, IInternetTransport *pTransport)
{
HRESULT hrTemp;
IStoreCallback *pCallback;
TraceCall("CIMAPSync::OnStatus");
IxpAssert(m_cRef > 0);
AssertSingleThreaded;
if (NULL != m_pCurrentCB)
pCallback = m_pCurrentCB;
else
pCallback = m_pDefCallback;
// Report status to UI component
if (NULL != pCallback)
{
hrTemp = pCallback->OnProgress(SOT_CONNECTION_STATUS, ixpStatus, 0,
m_rInetServerInfo.szServerName);
TraceError(hrTemp);
}
switch (ixpStatus)
{
case IXP_AUTHORIZED:
m_issCurrent = issAuthenticated;
// Clear any OnError's collected (typ. from one or more login rejections)
m_hrOperationResult = OLE_E_BLANK;
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_CONNCOMPLETE);
TraceError(hrTemp);
break;
case IXP_DISCONNECTED:
// If we're disconnecting due to reconnect attempt, do not abort operation
if (m_fReconnect)
{
// if we got disconnected reset the current and pending state
OnFolderExit();
m_issCurrent = issNotConnected;
m_fDisconnecting = FALSE; // We are now done with disconnection
break;
}
// Figure out if we were ever connected
if (OLE_E_BLANK == m_hrOperationResult)
{
if (issNotConnected == m_issCurrent)
m_hrOperationResult = IXP_E_FAILED_TO_CONNECT;
else
m_hrOperationResult = IXP_E_CONNECTION_DROPPED;
}
OnFolderExit();
FlushOperationQueue(issNotConnected, m_hrOperationResult);
// if we got disconnected reset the current and pending state
m_issCurrent = issNotConnected;
m_fDisconnecting = FALSE; // We are now done with disconnection
// There is only one case where _OnCmdComplete doesn't get the chance
// to issue the CFSM_EVENT_ERROR, and that's when we never even connect
if (CFSM_STATE_WAITFORCONN == m_cfsState)
{
// Move state machine along to abort this operation and reset
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_ERROR);
TraceError(hrTemp);
m_fTerminating = TRUE; // CFSM_EVENT_ERROR should make us go to CFSM_STATE_OPERATIONCOMPLETE
}
break;
case IXP_CONNECTED:
// if we get the first 'connected' then we are not yet
// AUTH'ed, so trasition into issNonAuthenticated if we
// get authorized, we transition into Authenticated
if (m_issCurrent == issNotConnected)
m_issCurrent = issNonAuthenticated;
break;
}
return S_OK; // Yippee, we have status
}
//***************************************************************************
// Function: OnError
// Description: See imnxport.idl for details.
//***************************************************************************
HRESULT CIMAPSync::OnError(IXPSTATUS ixpStatus, LPIXPRESULT pResult,
IInternetTransport *pTransport)
{
AssertSingleThreaded;
// Currently all OnError calls are due to logon/connection problems
// Not much we can do: there's no way to show error outside of OnComplete
// One thing we CAN do is to store error text. If we are disconnected next,
// we will have something to show the user
if (NULL != pResult->pszProblem)
StrCpyN(m_szOperationProblem, pResult->pszProblem, ARRAYSIZE(m_szOperationProblem));
if (NULL != pResult->pszResponse)
StrCpyN(m_szOperationDetails, pResult->pszResponse, ARRAYSIZE(m_szOperationDetails));
m_hrOperationResult = pResult->hrResult;
// Ignore all errors except for the following:
if (IXP_E_IMAP_LOGINFAILURE == pResult->hrResult)
{
HRESULT hrTemp;
HWND hwndParent;
hrTemp = GetParentWindow(0, &hwndParent);
if (FAILED(hrTemp))
{
// Not much we can do here!
TraceInfoTag(TAG_IMAPSYNC, _MSG("*** CIMAPSync::OnError received for %s operation",
sotToSz(m_sotCurrent)));
}
else
{
STOREERROR seErrorInfo;
// Display error to user ourselves
FillStoreError(&seErrorInfo, pResult->hrResult, pResult->dwSocketError, NULL, NULL);
CallbackDisplayError(hwndParent, seErrorInfo.hrResult, &seErrorInfo);
}
}
return S_OK;
} // OnError
//***************************************************************************
// Function: OnCommand
// Description: See imnxport.idl for details.
//***************************************************************************
HRESULT CIMAPSync::OnCommand(CMDTYPE cmdtype, LPSTR pszLine,
HRESULT hrResponse, IInternetTransport *pTransport)
{
IxpAssert(m_cRef > 0);
AssertSingleThreaded;
// We should never get this
AssertSz(FALSE, "*** Received ITransportCallback::OnCommand callback!!!");
return S_OK;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::GetParentWindow(DWORD dwReserved, HWND *phwndParent)
{
HRESULT hr = E_FAIL;
AssertSingleThreaded;
// Ask the callback recipient
if (NULL != m_pCurrentCB)
{
hr = m_pCurrentCB->GetParentWindow(dwReserved, phwndParent);
TraceError(hr);
}
else if (NULL != m_pDefCallback)
{
hr = m_pDefCallback->GetParentWindow(dwReserved, phwndParent);
TraceError(hr);
}
if (FAILED(hr))
{
// We're not supposed to put up any UI
*phwndParent = NULL;
}
return hr;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::GetAccount(LPDWORD pdwServerType, IImnAccount **ppAccount)
{
// Locals
HRESULT hr = E_UNEXPECTED;
// Invalid Args
Assert(ppAccount);
Assert(g_pAcctMan);
Assert(m_pszAccountID);
// Initialize
*ppAccount = NULL;
if (g_pAcctMan)
{
// Find the Account
IF_FAILEXIT(hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_pszAccountID, ppAccount));
// Set the server type
*pdwServerType = SRV_IMAP;
}
exit:
// Done
return(hr);
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::_ShowUserInfo(LPSTR pszTitle, LPSTR pszText1, LPSTR pszText2)
{
char szTitle[CCHMAX_STRINGRES];
char szUserInfo[2 * CCHMAX_STRINGRES];
LPSTR p;
HRESULT hr;
INT iTemp;
IStoreCallback *pCallback;
TraceCall("CIMAPSync::_ShowUserInfo");
AssertSingleThreaded;
// Check args
if (NULL == pszTitle || NULL == pszText1)
{
AssertSz(FALSE, "pszTitle and pszText1 cannot be NULL");
hr = TraceResult(E_INVALIDARG);
goto exit;
}
if (NULL != m_pCurrentCB)
pCallback = m_pCurrentCB;
else
pCallback = m_pDefCallback;
// Check if we have a callback to call
if (NULL == pCallback)
return S_OK; // Nothing to do here!
if (IS_INTRESOURCE(pszTitle))
{
LoadString(g_hLocRes, PtrToUlong(pszTitle), szTitle, ARRAYSIZE(szTitle));
pszTitle = szTitle;
}
p = szUserInfo;
if (IS_INTRESOURCE(pszText1))
p += LoadString(g_hLocRes, PtrToUlong(pszText1), szUserInfo, ARRAYSIZE(szUserInfo));
if (NULL != pszText2)
{
if (IS_INTRESOURCE(pszText2))
LoadString(g_hLocRes, PtrToUlong(pszText2), p, ARRAYSIZE(szUserInfo) -
(int) (p - szUserInfo));
else
StrCpyN(p, pszText2, ARRAYSIZE(szUserInfo) - (int) (p - szUserInfo));
}
hr = pCallback->OnPrompt(S_OK, szUserInfo, pszTitle, MB_OK, &iTemp);
TraceError(hr);
exit:
return hr;
}
//***************************************************************************
// Function: OnMailBoxUpdate
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnMailBoxUpdate(MBOX_MSGCOUNT *pNewMsgCount)
{
HRESULT hrTemp;
TraceCall("CIMAPSync::OnMailBoxUpdate");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pNewMsgCount);
// Handle EXISTS response - calculate number of new msgs, update m_dwMsgCount
if (pNewMsgCount->bGotExistsResponse)
{
// Since we are guaranteed to get all EXPUNGE responses, and since
// we decrement m_dwMsgCount for each EXPUNGE, number of new messages
// is difference between current EXISTS count and m_dwMsgCount.
if (m_fMsgCountValid)
{
if (pNewMsgCount->dwExists >= m_dwMsgCount)
m_dwNumNewMsgs += pNewMsgCount->dwExists - m_dwMsgCount;
}
m_dwMsgCount = pNewMsgCount->dwExists;
m_fMsgCountValid = TRUE;
// Make sure msg seq num <-> UID table is proper size for this mbox
// Record but otherwise ignore errors
hrTemp = m_pTransport->ResizeMsgSeqNumTable(pNewMsgCount->dwExists);
TraceError(hrTemp);
}
// New messages! Woo hoo!
if (m_dwNumNewMsgs > 0)
{
m_dwSyncToDo |= (m_dwSyncFolderFlags & SYNC_FOLDER_NEW_HEADERS);
hrTemp = _SyncHeader();
TraceError(hrTemp);
}
return S_OK;
}
//***************************************************************************
// Function: _OnMsgDeleted
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnMsgDeleted(DWORD dwDeletedMsgSeqNum)
{
DWORD dwDeletedMsgUID, dwHighestMSN;
HRESULT hr;
MESSAGEIDLIST midList;
MESSAGEID mid;
TraceCall("CIMAPSync::DeletedMsgNotification");
IxpAssert(m_cRef > 0);
IxpAssert(0 != dwDeletedMsgSeqNum);
// Regardless of outcome, an EXPUNGE means there's one less msg - update vars
if (m_fMsgCountValid)
m_dwMsgCount -= 1;
// Is this msg seq num within our translation range?
hr = m_pTransport->GetHighestMsgSeqNum(&dwHighestMSN);
if (SUCCEEDED(hr) && dwDeletedMsgSeqNum > dwHighestMSN)
return S_OK; // We got an EXPUNGE for a hdr we've never DL'ed
// Find out who got the axe
hr = m_pTransport->MsgSeqNumToUID(dwDeletedMsgSeqNum, &dwDeletedMsgUID);
if (FAILED(hr) || 0 == dwDeletedMsgUID)
{
// Failure here means we either got a bogus msg seq num, or we got an
// EXPUNGE during SELECT (before the tidFETCH_CACHED_FLAGS transaction).
// If the latter is true, it's no big deal since FETCHes will sync us up.
TraceResult(E_FAIL); // Record but otherwise ignore error
goto exit;
}
// Delete message from the cache. Note that we do not care about error
// because even in case of error, we must resequence the table
mid = (MESSAGEID)((DWORD_PTR)dwDeletedMsgUID);
midList.cAllocated = 0;
midList.cMsgs = 1;
midList.prgidMsg = &mid;
hr = m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, &midList, NULL, NULL);
TraceError(hr);
exit:
// Resequence our msg seq num <-> UID table
hr = m_pTransport->RemoveSequenceNum(dwDeletedMsgSeqNum);
TraceError(hr);
return S_OK;
}
//***************************************************************************
// Function: _OnFetchBody
// Purpose: This function handles the irtFETCH_BODY response type of
// IIMAPCallback::OnResponse.
//***************************************************************************
HRESULT CIMAPSync::_OnFetchBody(HRESULT hrFetchBodyResult,
FETCH_BODY_PART *pFetchBodyPart)
{
LPSTREAM lpstmRFC822; // Only used for RFC822.HEADER
HRESULT hr;
TraceCall("CIMAPSync::_OnFetchBody");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pFetchBodyPart);
IxpAssert(NULL != pFetchBodyPart->pszBodyTag);
IxpAssert(NULL != pFetchBodyPart->pszData);
IxpAssert(0 != pFetchBodyPart->dwMsgSeqNum);
// Initialize variables
hr = S_OK;
lpstmRFC822 = (LPSTREAM) pFetchBodyPart->lpFetchCookie2;
// Check for (and deal with) failure
if (FAILED(hrFetchBodyResult))
{
DWORD dwUID;
TraceResult(hrFetchBodyResult);
pFetchBodyPart->lpFetchCookie1 = fbpNONE;
if (NULL != lpstmRFC822)
{
lpstmRFC822->Release();
pFetchBodyPart->lpFetchCookie2 = NULL;
}
// Get the UID of this message
hr = m_pTransport->MsgSeqNumToUID(pFetchBodyPart->dwMsgSeqNum, &dwUID);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
NotifyMsgRecipients(dwUID, fCOMPLETED, NULL, hrFetchBodyResult, NULL);
goto exit;
}
// Identify this fetch body tag, if we haven't already
if (fbpNONE == pFetchBodyPart->lpFetchCookie1)
{
// First check for incoming body, then check for incoming header
if (0 == lstrcmpi(pFetchBodyPart->pszBodyTag, "RFC822") ||
0 == lstrcmpi(pFetchBodyPart->pszBodyTag, "BODY[]"))
{
pFetchBodyPart->lpFetchCookie1 = fbpBODY;
}
else if (0 == lstrcmpi(pFetchBodyPart->pszBodyTag, "RFC822.HEADER") ||
0 == lstrcmpi(pFetchBodyPart->pszBodyTag, "BODY[HEADER.FIELDS"))
{
pFetchBodyPart->lpFetchCookie1 = fbpHEADER;
// Create a stream
IxpAssert(NULL == lpstmRFC822);
hr = MimeOleCreateVirtualStream(&lpstmRFC822);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
pFetchBodyPart->lpFetchCookie2 = (LPARAM) lpstmRFC822;
}
else
{
AssertSz(FALSE, "What kind of tag is this?");
pFetchBodyPart->lpFetchCookie1 = fbpUNKNOWN;
}
}
// If this is a message body, update progress
if (fbpBODY == pFetchBodyPart->lpFetchCookie1)
{
DWORD dwUID;
hr = m_pTransport->MsgSeqNumToUID(pFetchBodyPart->dwMsgSeqNum, &dwUID);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
NotifyMsgRecipients(dwUID, fPROGRESS, pFetchBodyPart, S_OK, NULL);
}
// Append the data to a stream
if (NULL != lpstmRFC822)
{
DWORD dwNumBytesWritten;
IxpAssert(fbpHEADER == pFetchBodyPart->lpFetchCookie1);
hr = lpstmRFC822->Write(pFetchBodyPart->pszData,
pFetchBodyPart->dwSizeOfData, &dwNumBytesWritten);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
IxpAssert(dwNumBytesWritten == pFetchBodyPart->dwSizeOfData);
}
exit:
return S_OK;
}
//***************************************************************************
// Function: _OnUpdateMsg
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnUpdateMsg(WPARAM tid, HRESULT hrFetchCmdResult,
FETCH_CMD_RESULTS_EX *pFetchResults)
{
HRESULT hrTemp;
TraceCall("CIMAPSync::UpdateMsgNotification");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pFetchResults);
// Keep our msg seq num <-> UID table up to date
if (pFetchResults->bUID)
{
// Record error but otherwise ignore
hrTemp = m_pTransport->UpdateSeqNumToUID(pFetchResults->dwMsgSeqNum,
pFetchResults->dwUID);
TraceError(hrTemp);
}
else
{
HRESULT hr;
DWORD dwHighestMSN;
// No UID w/ FETCH resp means this is unsolicited: check that we already have hdr
hr = m_pTransport->GetHighestMsgSeqNum(&dwHighestMSN);
TraceError(hr);
if (SUCCEEDED(hr) && pFetchResults->dwMsgSeqNum > dwHighestMSN)
goto exit; // Can't translate MsgSeqNum to UID, typ. because svr is reporting
// flag updates on msgs which we haven't had hdrs DL'ed yet. No prob,
// if svr reported EXISTS correctly, we should be DL'ing hdrs shortly
// Either unsolicited FETCH, or server needs to learn to send UID for UID cmds
hr = m_pTransport->MsgSeqNumToUID(pFetchResults->dwMsgSeqNum, &pFetchResults->dwUID);
if (FAILED(hr) || 0 == pFetchResults->dwUID)
{
TraceResult(hr);
goto exit;
}
else
pFetchResults->bUID = TRUE;
}
// We classify our fetch responses as header downloads, body downloads,
// and flag updates.
if (pFetchResults->bEnvelope)
{
// We only get envelopes when we are asking for headers
Assert(fbpBODY != pFetchResults->lpFetchCookie1);
pFetchResults->lpFetchCookie1 = fbpHEADER;
}
switch (pFetchResults->lpFetchCookie1)
{
case fbpHEADER:
UpdateMsgHeader(tid, hrFetchCmdResult, pFetchResults);
break;
case fbpBODY:
UpdateMsgBody(tid, hrFetchCmdResult, pFetchResults);
break;
default:
AssertSz(fbpNONE == pFetchResults->lpFetchCookie1, "Unhandled FetchBodyPart type");
UpdateMsgFlags(tid, hrFetchCmdResult, pFetchResults);
break;
}
exit:
// If we allocated a stream, free it
if (NULL != pFetchResults->lpFetchCookie2)
((LPSTREAM)pFetchResults->lpFetchCookie2)->Release();
return S_OK;
}
//***************************************************************************
// Function: UpdateMsgHeader
//
// Purpose:
// This function takes a message header returned via FETCH response,
// caches it, and notifies the view.
//
// Arguments:
// WPARAM wpTransactionID [in] - transaction ID of fetch response.
// Currently ignored.
// HRESULT hrFetchCmdResult [in] - success/failure of FETCH cmd
// const FETCH_CMD_RESULTS_EX *pFetchResults [in] - the information from
// the FETCH response.
//***************************************************************************
HRESULT CIMAPSync::UpdateMsgHeader( WPARAM tid,
HRESULT hrFetchCmdResult,
FETCH_CMD_RESULTS_EX *pFetchResults)
{
HRESULT hr;
MESSAGEINFO miMsgInfo={0};
TraceCall("CIMAPSync::UpdateMsgHeader");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pFetchResults);
IxpAssert(pFetchResults->bUID);
IxpAssert(fbpHEADER == pFetchResults->lpFetchCookie1);
// Make sure we have everything we need
if (FAILED(hrFetchCmdResult))
{
// Error on FETCH response, forget this header
hr = TraceResult(hrFetchCmdResult);
goto exit;
}
if (NULL == pFetchResults->lpFetchCookie2 && FALSE == pFetchResults->bEnvelope)
{
// I don't do ANYTHING without an RFC822.HEADER stream or envelope
hr = TraceResult(E_INVALIDARG);
goto exit;
}
// First, check if we already have this header cached
miMsgInfo.idMessage = (MESSAGEID)((DWORD_PTR)pFetchResults->dwUID);
hr = m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &miMsgInfo, NULL);
if (DB_S_FOUND == hr)
{
// No need for alarm, we'll just swallow this header
m_pFolder->FreeRecord(&miMsgInfo);
goto exit; // We already have this header - we shouldn't have gotten this
// On some IMAP servers, if you UID FETCH <highestCachedUID + 1>:*
// you get a fetch response for highestCachedUID! Ignore this fetch result.
}
m_pFolder->FreeRecord(&miMsgInfo);
// Cache this header, since it's not in our cache already
hr = Fill_MESSAGEINFO(pFetchResults, &miMsgInfo);
if (FAILED(hr))
{
FreeMessageInfo(&miMsgInfo); // There could be a couple of fields in there
TraceResult(hr);
goto exit;
}
hr = m_pFolder->InsertRecord(&miMsgInfo);
if (SUCCEEDED(hr) && 0 == (ARF_READ & miMsgInfo.dwFlags))
m_fNewMail = TRUE;
FreeMessageInfo(&miMsgInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
// Bump up synchronize headers progress
// Currently the only way we can get a hdr is through sync-up. Later on we'll
// be able to get individual hdrs at which point this code should be updated
if (TRUE)
{
DWORD dwNumExpectedMsgs;
// Recalculate total number of expected messages
if (m_fMsgCountValid &&
m_dwMsgCount + m_dwNumHdrsDLed + 1 >= pFetchResults->dwMsgSeqNum)
{
IxpAssert(m_dwNumHdrsDLed < pFetchResults->dwMsgSeqNum);
dwNumExpectedMsgs = m_dwMsgCount + m_dwNumHdrsDLed + 1 -
pFetchResults->dwMsgSeqNum;
if (dwNumExpectedMsgs != m_dwNumHdrsToDL)
{
// Record but otherwise ignore this fact
TraceInfoTag(TAG_IMAPSYNC, _MSG("*** dwNumExpectedMsgs = %lu, m_dwNumHdrsToDL = %lu!",
dwNumExpectedMsgs, m_dwNumHdrsToDL));
}
}
m_dwNumHdrsDLed += 1;
if (pFetchResults->bMsgFlags && ISFLAGCLEAR(pFetchResults->mfMsgFlags, IMAP_MSG_SEEN))
m_dwNumUnreadDLed += 1;
if (NULL != m_pCurrentCB && SOT_SYNC_FOLDER == m_sotCurrent)
{
HRESULT hrTemp;
hrTemp = m_pCurrentCB->OnProgress(SOT_SYNC_FOLDER,
m_dwNumHdrsDLed, dwNumExpectedMsgs, m_pszFldrLeafName);
TraceError(hrTemp);
}
}
exit:
return hr;
}
//***************************************************************************
// Function: UpdateMsgBody
//
// Purpose:
// This function takes a message body returned via FETCH response,
// caches it, and notifies all interested parties (there may be more
// than one).
//
// Arguments:
// WPARAM wpTransactionID [in] - transaction ID of fetch response.
// Currently ignored.
// HRESULT hrFetchCmdResult [in] - success/failure of FETCH cmd
// const FETCH_CMD_RESULTS_EX *pFetchResults [in] - the information from
// the FETCH response.
//***************************************************************************
HRESULT CIMAPSync::UpdateMsgBody( WPARAM tid,
HRESULT hrFetchCmdResult,
FETCH_CMD_RESULTS_EX *pFetchResults)
{
TraceCall("CIMAPSync::UpdateMsgBody");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pFetchResults);
IxpAssert(pFetchResults->bUID);
IxpAssert(fbpBODY == pFetchResults->lpFetchCookie1);
// Record any fetch error
TraceError(hrFetchCmdResult);
// We used to call NotifyMsgRecipients(fCOMPLETED) here, but since we only
// get one body at a time, we should defer to _OnCmdComplete. This is because
// FETCH body responses have multiple failure modes: tagged OK with no body,
// tagged NO with no body, tagged OK with literal of size 0 (Netscape). To
// easily avoid calling NotifyMsgRecipients twice, don't call from here.
// It's possible to have FETCH response with no body: If you fetch an expunged
// msg from a Netscape svr, you get a literal of size 0. Check for this case.
if (SUCCEEDED(hrFetchCmdResult) && FALSE == m_fGotBody)
hrFetchCmdResult = STORE_E_EXPIRED;
if (FAILED(hrFetchCmdResult) &&
(SUCCEEDED(m_hrOperationResult) || OLE_E_BLANK == m_hrOperationResult))
{
// We don't have an error set yet. Record this error
m_hrOperationResult = hrFetchCmdResult;
}
return S_OK;
}
//***************************************************************************
// Function: UpdateMsgFlags
//
// Purpose:
// This function takes a message's flags returned via FETCH response,
// updates the cache, and notifies the view.
//
// Arguments:
// WPARAM wpTransactionID [in] - transaction ID of fetch response.
// Currently ignored.
// HRESULT hrFetchCmdResult [in] - success/failure of FETCH cmd
// const FETCH_CMD_RESULTS_EX *pFetchResults [in] - the information from
// the FETCH response.
//***************************************************************************
HRESULT CIMAPSync::UpdateMsgFlags( WPARAM tid,
HRESULT hrFetchCmdResult,
FETCH_CMD_RESULTS_EX *pFetchResults)
{
HRESULT hr = S_OK;
MESSAGEINFO miMsgInfo;
MESSAGEFLAGS mfFlags;
BOOL fFreeMsgInfo = FALSE;
TraceCall("CIMAPSync::UpdateMsgFlags");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pFetchResults);
IxpAssert(pFetchResults->bUID);
IxpAssert(fbpNONE == pFetchResults->lpFetchCookie1);
IxpAssert(0 == pFetchResults->lpFetchCookie2);
if (FAILED(hrFetchCmdResult))
{
// Error on FETCH response, forget this flag update
hr = TraceResult(hrFetchCmdResult);
goto exit;
}
// We expect that if there is no header and no body, that this is either
// a solicited or unsolicited flag update
if (FALSE == pFetchResults->bMsgFlags)
{
hr = S_OK; // We'll just ignore this fetch response. No need to wig out
goto exit;
}
// Get the header for this message
miMsgInfo.idMessage = (MESSAGEID)((DWORD_PTR)pFetchResults->dwUID);
hr = m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &miMsgInfo, NULL);
if (DB_S_FOUND != hr)
{
TraceError(hr);
goto exit;
}
// Convert IMAP flags to ARF_* flags
fFreeMsgInfo = TRUE;
mfFlags = miMsgInfo.dwFlags;
mfFlags &= ~DwConvertIMAPtoARF(IMAP_MSG_ALLFLAGS); // Clear old IMAP flags
mfFlags |= DwConvertIMAPtoARF(pFetchResults->mfMsgFlags); // Set IMAP flags
// Are the new flags any different from our cached ones?
if (mfFlags != miMsgInfo.dwFlags)
{
// Save new flags
miMsgInfo.dwFlags = mfFlags;
hr = m_pFolder->UpdateRecord(&miMsgInfo);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
exit:
if (fFreeMsgInfo)
m_pFolder->FreeRecord(&miMsgInfo);
return hr;
}
//***************************************************************************
// Function: _OnApplFlags
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnApplFlags(WPARAM tid, IMAP_MSGFLAGS imfApplicableFlags)
{
TraceCall("CIMAPSync::_OnApplFlags");
// Save flags and process after SELECT is complete. DO NOT PROCESS HERE
// because this response can be part of previously selected folder.
return S_OK;
}
//***************************************************************************
// Function: _OnPermFlags
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnPermFlags(WPARAM tid, IMAP_MSGFLAGS imfApplicableFlags, LPSTR lpszResponseText)
{
TraceCall("CIMAPSync::PermanentFlagsNotification");
IxpAssert(m_cRef > 0);
// Save flags and process after SELECT is complete. DO NOT PROCESS HERE
// because this response can be part of previously selected folder.
return S_OK;
}
//***************************************************************************
// Function: _OnUIDValidity
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnUIDValidity(WPARAM tid, DWORD dwUIDValidity, LPSTR lpszResponseText)
{
TraceCall("CIMAPSync::UIDValidityNotification");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != lpszResponseText);
// Save UIDVALIDITY and process after SELECT is complete. DO NOT PROCESS HERE
// because this response can be part of previously selected folder.
m_dwUIDValidity = dwUIDValidity;
return S_OK;
}
//***************************************************************************
// Function: _OnReadWriteStatus
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnReadWriteStatus(WPARAM tid, BOOL bReadWrite, LPSTR lpszResponseText)
{
TraceCall("CIMAPSync::_OnReadWriteStatus");
IxpAssert(NULL != lpszResponseText);
// Save status and process after SELECT is complete. DO NOT PROCESS HERE
// because this response can be part of previously selected folder.
// I'm ignoring above statement because UW server sends READ-ONLY unilaterally.
// Above statement isn't currently valid anyway, I don't re-use connection if
// it's in the middle of SELECT (found out how bad it was after I tried it).
// Look for READ-WRITE to READ-ONLY transition
if (rwsUNINITIALIZED != m_rwsReadWriteStatus)
{
if (rwsREAD_WRITE == m_rwsReadWriteStatus && FALSE == bReadWrite)
{
HRESULT hrTemp;
hrTemp = _ShowUserInfo(MAKEINTRESOURCE(idsAthenaMail),
MAKEINTRESOURCE(idsIMAPFolderReadOnly), lpszResponseText);
TraceError(hrTemp);
}
}
// Save current read-write status for future reference
if (bReadWrite)
m_rwsReadWriteStatus = rwsREAD_WRITE;
else
m_rwsReadWriteStatus = rwsREAD_ONLY;
return S_OK;
}
//***************************************************************************
// Function: TryCreateNotification
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnTryCreate(WPARAM tid, LPSTR lpszResponseText)
{
TraceCall("CIMAPSync::TryCreateNotification");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != lpszResponseText);
// Save response and process after SELECT is complete. DO NOT PROCESS HERE
// because this response can be part of previously selected folder.
return S_OK;
}
//***************************************************************************
// Function: SearchResponseNotification
// Description: See imnxport.idl (this is part of IIMAPCallback).
//***************************************************************************
HRESULT CIMAPSync::_OnSearchResponse(WPARAM tid, IRangeList *prlSearchResults)
{
TraceCall("CIMAPSync::SearchResponseNotification");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != prlSearchResults);
// Process search response here (currently does nothing)
return S_OK;
}
//***************************************************************************
// Function: OnCmdComplete
// Description: See imnxport.idl (this is part of IIMAPCallback).
//
// For a CIMAPFolder class to be useful, it must enter the SELECTED state
// on the IMAP server. This function is written so that entering the SELECTED
// state is done in an orderly fashion: First the login, then the SELECT
// command.
//
// Once we are in SELECTED state, we can send IMAP commands at any time.
//***************************************************************************
HRESULT CIMAPSync::_OnCmdComplete(WPARAM tid, LPARAM lParam, HRESULT hrCompletionResult,
LPCSTR lpszResponseText)
{
TCHAR szFmt[CCHMAX_STRINGRES];
IStoreCallback *pCallback = NULL;
STOREOPERATIONTYPE sotOpType = SOT_INVALID;
BOOL fCompletion = FALSE;
BOOL fCreateDone = FALSE,
fDelFldrFromCache = FALSE,
fSuppressDetails = FALSE;
HRESULT hrTemp;
TraceCall("CIMAPSync::CmdCompletionNotification");
IxpAssert(NULL != lpszResponseText);
// Initialize variables
*szFmt = NULL;
// If we got any new unread messages, play sound and update tray icon
// Do it here instead of case tidFETCH_NEW_HDRS because if we were IDLE when we got
// new mail, m_sotCurrent is SOT_INVALID and we exit on the next if-statement
if (tidFETCH_NEW_HDRS == tid && m_fNewMail && m_fInbox &&
(NULL != m_pDefCallback || NULL != m_pCurrentCB))
{
IStoreCallback *pCB;
pCB = m_pDefCallback;
if (NULL == pCB)
pCB = m_pCurrentCB;
hrTemp = pCB->OnProgress(SOT_NEW_MAIL_NOTIFICATION, m_dwNumUnreadDLed, 0, NULL);
TraceError(hrTemp);
m_fNewMail = FALSE; // We've notified user
}
// We want to do the following even if there is no current operation
switch (tid)
{
case tidFETCH_NEW_HDRS:
case tidFETCH_CACHED_FLAGS:
case tidEXPUNGE:
case tidNOOP:
m_lSyncFolderRefCount -= 1;
break;
}
// We don't do a thing if there is no current operation (typ. means OnComplete
// already sent)
if (SOT_INVALID == m_sotCurrent)
return S_OK;
// Find out if this is a significant command which just completed
switch (tid)
{
case tidSELECTION:
// Select failure results in failure of current operation (eg, SOT_GET_MESSAGE
// or SOT_SYNC_FOLDER) but does not otherwise invoke a call to OnComplete
// This transaction ID identifies our mailbox SELECT attempt
if (SUCCEEDED(hrCompletionResult))
{
FOLDERINFO fiFolderInfo;
m_issCurrent = issSelected;
m_idSelectedFolder = m_idFolder;
// Set m_fInbox for new mail notification purposes
Assert(FALSE == m_fInbox); // Should only get set to TRUE in one place
hrTemp = m_pStore->GetFolderInfo(m_idSelectedFolder, &fiFolderInfo);
if (SUCCEEDED(hrTemp))
{
if (FOLDER_INBOX == fiFolderInfo.tySpecial)
m_fInbox = TRUE;
m_pStore->FreeRecord(&fiFolderInfo);
}
// Check if cached messages are still applicable to this folder
hrCompletionResult = CheckUIDValidity();
if (FAILED(hrCompletionResult))
{
fCompletion = TRUE;
LoadString(g_hLocRes, idsIMAPUIDValidityError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
break;
}
// Restore to-do flags
m_dwSyncToDo = m_dwSyncFolderFlags;
Assert(0 == (SYNC_FOLDER_NOOP & m_dwSyncFolderFlags)); // Should not be a standing order
if (ISFLAGSET(m_dwSyncFolderFlags, (SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS)))
m_dwSyncToDo |= SYNC_FOLDER_NOOP; // Subsequent FULL sync's can be replaced with NOOP
// Inform the Connection FSM of this event
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_SELECTCOMPLETE);
TraceError(hrTemp);
}
else
{
// Report error to user
fCompletion = TRUE;
LoadString(g_hLocRes, idsIMAPSelectFailureTextFmt, szFmt, ARRAYSIZE(szFmt));
wnsprintf(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), szFmt, (m_pszFldrLeafName ? m_pszFldrLeafName : ""));
}
break; // case tidSELECTION
case tidFETCH_NEW_HDRS:
fCompletion = TRUE; // We'll assume this unless we find otherwise
// This transaction ID identifies our attempt to fetch new msg headers
if (FAILED(hrCompletionResult))
{
LoadString(g_hLocRes, idsIMAPNewMsgDLErrText, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
else
{
if (FALSE == m_fMsgCountValid)
{
DWORD dwCachedCount;
// Svr didn't give us EXISTS (typ. NSCP v2.0). Assume m_cFilled == EXISTS
hrTemp = m_pFolder->GetRecordCount(IINDEX_PRIMARY, &dwCachedCount);
TraceError(hrTemp); // Record error but otherwise ignore
if (SUCCEEDED(hrTemp))
{
m_dwMsgCount = dwCachedCount; // I sure hope this is correct!
m_fMsgCountValid = TRUE;
}
}
// Launch next synchronization operation
hrCompletionResult = _SyncHeader();
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
else if (STORE_S_NOOP != hrCompletionResult || m_lSyncFolderRefCount > 0 ||
CFSM_STATE_WAITFORHDRSYNC != m_cfsState)
{
// Successfully launched next sync operation, so we're not done yet
fCompletion = FALSE;
}
}
if (SUCCEEDED(hrCompletionResult) && fCompletion)
{
// We're done with header sync (but not with the operation)
fCompletion = FALSE;
// Inform the Connection FSM of this event
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_HDRSYNCCOMPLETE);
TraceError(hrTemp);
}
break; // case tidFETCH_NEW_HDRS
case tidFETCH_CACHED_FLAGS:
fCompletion = TRUE; // We'll assume this unless we find otherwise
// If any errors occurred, bail out
if (FAILED(hrCompletionResult))
{
LoadString(g_hLocRes, idsIMAPOldMsgUpdateFailure, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
else
{
// Delete all msgs deleted from server since last sync-up
hrCompletionResult = SyncDeletedMessages();
if (FAILED(hrCompletionResult))
{
LoadString(g_hLocRes, idsIMAPMsgDeleteSyncErrText, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
// Check we if we did a full sync
if (ISFLAGSET(m_dwSyncFolderFlags, (SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS)))
m_fDidFullSync = TRUE;
// Launch next synchronization operation
hrCompletionResult = _SyncHeader();
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
break;
}
else if (STORE_S_NOOP != hrCompletionResult || m_lSyncFolderRefCount > 0 ||
CFSM_STATE_WAITFORHDRSYNC != m_cfsState)
{
// Successfully launched next sync operation, so we're not done yet
fCompletion = FALSE;
}
}
if (SUCCEEDED(hrCompletionResult) && fCompletion)
{
// We're done with header sync (but not with the operation)
fCompletion = FALSE;
// Inform the Connection FSM of this event
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_HDRSYNCCOMPLETE);
TraceError(hrTemp);
}
break; // case tidFETCH_CACHED_FLAGS
case tidEXPUNGE:
fCompletion = TRUE; // We'll assume this unless we find otherwise
// Launch next synchronization operation
if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
{
hrCompletionResult = _SyncHeader();
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
break;
}
else if (STORE_S_NOOP != hrCompletionResult || m_lSyncFolderRefCount > 0 ||
CFSM_STATE_WAITFORHDRSYNC != m_cfsState)
{
// Successfully launched next sync operation, so we're not done yet
fCompletion = FALSE;
}
}
if (SUCCEEDED(hrCompletionResult) && fCompletion)
{
// We're done with header sync (but not with the operation)
fCompletion = FALSE;
// Inform the Connection FSM of this event
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_HDRSYNCCOMPLETE);
TraceError(hrTemp);
}
break; // case tidEXPUNGE
case tidBODYMSN:
if (SUCCEEDED(hrCompletionResult))
{
// We now have the MsgSeqNumToUID translation. Get the body
hrCompletionResult = _EnqueueOperation(tidBODY, lParam, icFETCH_COMMAND,
NULL, uiNORMAL_PRIORITY);
if (FAILED(hrCompletionResult))
TraceResult(hrCompletionResult);
else
break;
}
// *** If tidBODYMSN failed, FALL THROUGH and code below will handle failure
case tidBODY:
// As commented in CIMAPSync::UpdateMsgBody, FETCH has many failure modes
if (SUCCEEDED(hrCompletionResult))
{
if (OLE_E_BLANK != m_hrOperationResult && FAILED(m_hrOperationResult))
hrCompletionResult = m_hrOperationResult;
else if (FALSE == m_fGotBody)
hrCompletionResult = STORE_E_EXPIRED;
}
// Load error info if this failed
if (FAILED(hrCompletionResult))
LoadString(g_hLocRes, idsIMAPBodyFetchFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
// Commit message body to stream (or not, depending on success/failure)
NotifyMsgRecipients(lParam, fCOMPLETED, NULL, hrCompletionResult, m_szOperationProblem);
m_fGotBody = FALSE;
fCompletion = TRUE;
break;
case tidNOOP:
fCompletion = TRUE; // We'll assume this unless we find otherwise
// Launch next synchronization operation
if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
{
hrCompletionResult = _SyncHeader();
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
break;
}
else if (STORE_S_NOOP != hrCompletionResult || m_lSyncFolderRefCount > 0 ||
CFSM_STATE_WAITFORHDRSYNC != m_cfsState)
{
// Successfully launched next sync operation, so we're not done yet
fCompletion = FALSE;
}
}
if (SUCCEEDED(hrCompletionResult) && fCompletion)
{
// We're done with header sync (but not with the operation)
fCompletion = FALSE;
// Inform the Connection FSM of this event
hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_HDRSYNCCOMPLETE);
TraceError(hrTemp);
}
break; // case tidNOOP
case tidMARKMSGS:
{
MARK_MSGS_INFO *pMarkMsgInfo = (MARK_MSGS_INFO *) lParam;
// We're done now whether or not we succeeded/failed
sotOpType = pMarkMsgInfo->sotOpType;
pCallback = m_pCurrentCB;
SafeRelease(pMarkMsgInfo->pMsgRange);
// Defer freeing MessageIDList until we have time to use it
fCompletion = TRUE;
IxpAssert(NULL != pMarkMsgInfo);
TraceError(hrCompletionResult);
if (SUCCEEDED(hrCompletionResult))
{
// Update the IMessageFolder with the new server state
hrCompletionResult = m_pFolder->SetMessageFlags(pMarkMsgInfo->pList,
&pMarkMsgInfo->afFlags, NULL, NULL);
TraceError(hrCompletionResult);
}
SafeMemFree(pMarkMsgInfo->pList);
delete pMarkMsgInfo;
}
break; // case tidMARKMSGS
case tidCOPYMSGS:
{
IMAP_COPYMOVE_INFO *pCopyMoveInfo = (IMAP_COPYMOVE_INFO *) lParam;
BOOL fCopyDone;
// Check if this is the last SELECT command we sent out
IxpAssert(NULL != lParam);
fCopyDone = FALSE;
TraceError(hrCompletionResult);
if (FALSE == fCopyDone && SUCCEEDED(hrCompletionResult) &&
(COPY_MESSAGE_MOVE & pCopyMoveInfo->dwOptions))
{
ADJUSTFLAGS afFlags;
// Delete source messages as part of the move
afFlags.dwAdd = ARF_ENDANGERED;
afFlags.dwRemove = 0;
hrCompletionResult = _SetMessageFlags(SOT_COPYMOVE_MESSAGE, pCopyMoveInfo->pList,
&afFlags, m_pCurrentCB);
if (E_PENDING == hrCompletionResult)
{
hrCompletionResult = S_OK; // Suppress error
}
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
fCopyDone = TRUE;
}
}
else
fCopyDone = TRUE;
if (FAILED(hrCompletionResult))
{
// Inform user of the error
IxpAssert(fCopyDone);
LoadString(g_hLocRes, idsIMAPCopyMsgsFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
// Whether or not copy is done, we have to free the data
SafeMemFree(pCopyMoveInfo->pList);
SafeRelease(pCopyMoveInfo->pCopyRange);
if (fCopyDone)
{
// Set up callback information for OnComplete
sotOpType = SOT_COPYMOVE_MESSAGE;
pCallback = m_pCurrentCB;
fCompletion = TRUE;
}
delete pCopyMoveInfo;
}
break; // case tidCOPYMSGS
case tidUPLOADMSG:
{
APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) lParam;
// We're done the upload, whether the APPEND succeeded or failed
SafeMemFree(pAppendInfo->pszMsgFlags);
SafeRelease(pAppendInfo->lpstmMsg);
sotOpType = SOT_PUT_MESSAGE;
pCallback = m_pCurrentCB;
fCompletion = TRUE;
delete pAppendInfo;
// Inform the user of any errors
if (FAILED(hrCompletionResult))
LoadString(g_hLocRes, idsIMAPAppendFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidUPLOADMSG
case tidPREFIXLIST:
case tidPREFIX_HC:
case tidPREFIX_CREATE:
case tidFOLDERLIST:
case tidFOLDERLSUB:
case tidHIERARCHYCHAR_LIST_B:
case tidHIERARCHYCHAR_CREATE:
case tidHIERARCHYCHAR_LIST_C:
case tidHIERARCHYCHAR_DELETE:
case tidSPECIALFLDRLIST:
case tidSPECIALFLDRLSUB:
case tidSPECIALFLDRSUBSCRIBE:
hrCompletionResult = DownloadFoldersSequencer(tid, lParam,
hrCompletionResult, lpszResponseText, &fCompletion);
break; // DownloadFoldersSequencer transactions
case tidCREATE:
if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
{
CREATE_FOLDER_INFO *pcfi = (CREATE_FOLDER_INFO *) lParam;
// If CREATE returns tagged NO, folder may already exist. Issue LIST and find out!
if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
{
pcfi->dwFlags |= CFI_CREATEFAILURE;
StrCpyN(m_szOperationDetails, lpszResponseText, ARRAYSIZE(m_szOperationDetails));
}
// Add the folder to our foldercache, by listing it
hrCompletionResult = _EnqueueOperation(tidCREATELIST, lParam, icLIST_COMMAND,
pcfi->pszFullFolderPath, uiNORMAL_PRIORITY);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
fCreateDone = TRUE;
LoadString(g_hLocRes, idsIMAPCreateListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
}
else
{
// If we failed, notify user and free the folder pathname string
TraceResult(hrCompletionResult);
if (NULL != lParam)
fCreateDone = TRUE;
// Inform the user of the error.
LoadString(g_hLocRes, idsIMAPCreateFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidCREATE
case tidCREATELIST:
if (SUCCEEDED(hrCompletionResult) &&
(CFI_RECEIVEDLISTING & ((CREATE_FOLDER_INFO *)lParam)->dwFlags))
{
// We received a listing of this mailbox, so it's now cached. Subscribe it!
hrCompletionResult = _EnqueueOperation(tidCREATESUBSCRIBE, lParam,
icSUBSCRIBE_COMMAND, ((CREATE_FOLDER_INFO *)lParam)->pszFullFolderPath,
uiNORMAL_PRIORITY);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
fCreateDone = TRUE;
LoadString(g_hLocRes, idsIMAPCreateSubscribeFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
}
else
{
CREATE_FOLDER_INFO *pcfi = (CREATE_FOLDER_INFO *) lParam;
// Check if we were issuing a LIST in response to a failed CREATE command
if (CFI_CREATEFAILURE & pcfi->dwFlags)
{
LoadString(g_hLocRes, idsIMAPCreateFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
fSuppressDetails = TRUE; // Use response line from previous CREATE failure
hrCompletionResult = IXP_E_IMAP_TAGGED_NO_RESPONSE;
fCreateDone = TRUE;
break;
}
TraceError(hrCompletionResult);
if (SUCCEEDED(hrCompletionResult))
{
// The LIST was OK, but no folder name returned. This may mean
// we've assumed an incorrect hierarchy character
AssertSz(FALSE, "You might have an incorrect hierarchy char, here.");
hrCompletionResult = TraceResult(E_FAIL);
}
// If we failed, notify user and free the folder pathname string
if (NULL != lParam)
fCreateDone = TRUE;
// Inform the user of the error.
LoadString(g_hLocRes, idsIMAPCreateListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidCREATELIST
case tidCREATESUBSCRIBE:
// Whether we succeeded or not, free the CREATE_FOLDER_INFO
// (We're at the end of the create folder sequence)
if (NULL != lParam)
fCreateDone = TRUE;
// Remove this folder from LISTed folder list, if we were listing folders (this
// way the special folder we created doesn't get marked as unsubscribed)
if (NULL != m_pListHash && NULL != lParam)
{
CREATE_FOLDER_INFO *pcfiCreateInfo = (CREATE_FOLDER_INFO *) lParam;
FOLDERID idTemp;
hrTemp = m_pListHash->Find(
ImapUtil_ExtractLeafName(pcfiCreateInfo->pszFullFolderPath, m_cRootHierarchyChar),
fREMOVE, (void **) &idTemp);
TraceError(hrTemp);
}
// Check for errors.
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPCreateSubscribeFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
else if (NULL != lParam)
{
// Update the subscription status on the folder
IxpAssert(FOLDERID_INVALID != ((CREATE_FOLDER_INFO *)lParam)->idFolder);
hrCompletionResult = m_pStore->SubscribeToFolder(
((CREATE_FOLDER_INFO *)lParam)->idFolder, fSUBSCRIBE, NOSTORECALLBACK);
TraceError(hrCompletionResult);
}
break; // case tidCREATESUBSCRIBE
case tidDELETEFLDR:
DELETE_FOLDER_INFO *pdfi;
pdfi = (DELETE_FOLDER_INFO *)lParam;
if (SUCCEEDED(hrCompletionResult))
{
// Unsubscribe the folder to complete the delete process
_EnqueueOperation(tidDELETEFLDR_UNSUBSCRIBE, lParam, icUNSUBSCRIBE_COMMAND,
pdfi->pszFullFolderPath, uiNORMAL_PRIORITY);
}
else
{
// If I unsubscribe a failed delete, user might never realize he has this
// folder kicking around. Therefore, don't unsubscribe.
// We won't be needing this information, anymore
if (pdfi)
{
MemFree(pdfi->pszFullFolderPath);
MemFree(pdfi);
}
// Inform the user of the error
IxpAssert(SOT_DELETE_FOLDER == m_sotCurrent);
sotOpType = m_sotCurrent;
pCallback = m_pCurrentCB;
fCompletion = TRUE;
LoadString(g_hLocRes, idsIMAPDeleteFldrFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidDELETEFLDR
case tidDONT_CARE:
hrCompletionResult = S_OK; // Suppress all error
break;
case tidDELETEFLDR_UNSUBSCRIBE:
// This folder is already deleted, so even if unsubscribe fails, delete from cache
fDelFldrFromCache = TRUE;
// Inform the user of any errors
if (FAILED(hrCompletionResult))
{
LoadString(g_hLocRes, idsIMAPDeleteFldrUnsubFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidDELETEFLDR_UNSUBSCRIBE
case tidCLOSE:
fCompletion = TRUE;
OnFolderExit();
m_issCurrent = issAuthenticated;
hrCompletionResult = S_OK; // Suppress error
break;
case tidSUBSCRIBE:
{
UINT uiErrorFmtID; // In case of error
uiErrorFmtID = 0;
fCompletion = TRUE;
if (SUCCEEDED(hrCompletionResult))
{
IxpAssert(FOLDERID_INVALID != m_idCurrent);
// Update store subscription status
hrCompletionResult = m_pStore->SubscribeToFolder(m_idCurrent, m_fSubscribe, NOSTORECALLBACK);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
uiErrorFmtID = m_fSubscribe ? idsIMAPSubscrAddErrorFmt :
idsIMAPUnsubRemoveErrorFmt;
}
}
else
{
TraceResult(hrCompletionResult);
uiErrorFmtID = m_fSubscribe ? idsIMAPSubscribeFailedFmt : idsIMAPUnsubscribeFailedFmt;
}
// Load error message, if an error occurred
if (FAILED(hrCompletionResult))
{
FOLDERINFO fiFolderInfo;
LoadString(g_hLocRes, uiErrorFmtID, szFmt, ARRAYSIZE(szFmt));
hrTemp = m_pStore->GetFolderInfo(m_idCurrent, &fiFolderInfo);
if (FAILED(hrTemp))
{
// Time to lie, cheat and STEAL!!!
TraceResult(hrTemp);
ZeroMemory(&fiFolderInfo, sizeof(fiFolderInfo));
fiFolderInfo.pszName = PszDupA(c_szFolderV1);
}
wnsprintf(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), szFmt, fiFolderInfo.pszName);
m_pStore->FreeRecord(&fiFolderInfo);
} // if (FAILED(hrCompletionResult))
} // case tidSUBSCRIBE
break; // case tidSUBSCRIBE
case tidRENAME:
case tidRENAMESUBSCRIBE:
case tidRENAMELIST:
case tidRENAMERENAME:
case tidRENAMESUBSCRIBE_AGAIN:
case tidRENAMEUNSUBSCRIBE:
hrCompletionResult = RenameSequencer(tid, lParam, hrCompletionResult,
lpszResponseText, &fCompletion);
break; // Rename operations
case tidSTATUS:
IxpAssert(FOLDERID_INVALID != (FOLDERID)lParam);
fCompletion = TRUE;
if (FAILED(hrCompletionResult))
{
FOLDERINFO fiFolderInfo;
// Construct descriptive error message
LoadString(g_hLocRes, idsGetUnreadCountFailureFmt, szFmt, ARRAYSIZE(szFmt));
if (SUCCEEDED(m_pStore->GetFolderInfo((FOLDERID) lParam, &fiFolderInfo)))
{
wnsprintf(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), szFmt, fiFolderInfo.pszName, m_szAccountName);
m_pStore->FreeRecord(&fiFolderInfo);
}
}
break;
default:
AssertSz(FALSE, "Unhandled transaction ID!");
break; // default case
}
// If we've finished a create folder (success/failure), tell them he can nuke us, now
if (fCreateDone)
{
CREATE_FOLDER_INFO *pcfiCreateInfo = (CREATE_FOLDER_INFO *)lParam;
if (FOLDER_NOTSPECIAL == pcfiCreateInfo->dwFinalSfType)
{
IxpAssert(SOT_INVALID != m_sotCurrent);
IxpAssert(PCO_NONE == pcfiCreateInfo->pcoNextOp); // Regular fldr creation no longer has any post-ops
fCompletion = TRUE;
pCallback = m_pCurrentCB;
sotOpType = m_sotCurrent;
MemFree(pcfiCreateInfo->pszFullFolderPath);
delete pcfiCreateInfo;
}
else
{
// We trying to create all the special folders: move on to the next one
if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
{
hrCompletionResult = CreateNextSpecialFolder(pcfiCreateInfo, &fCompletion);
TraceError(hrCompletionResult);
}
}
}
// If we've successfully deleted a folder, remove it from the foldercache
if (fDelFldrFromCache)
{
DELETE_FOLDER_INFO *pdfi = (DELETE_FOLDER_INFO *)lParam;
hrCompletionResult = DeleteFolderFromCache(pdfi->idFolder, fRECURSIVE);
if (FAILED(hrCompletionResult))
LoadString(g_hLocRes, idsErrDeleteCachedFolderFail, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
MemFree(pdfi->pszFullFolderPath);
MemFree(pdfi);
IxpAssert(SOT_DELETE_FOLDER == m_sotCurrent);
sotOpType = m_sotCurrent;
pCallback = m_pCurrentCB;
fCompletion = TRUE;
}
// Report command completion
if (fCompletion)
{
CONN_FSM_EVENT cfeEvent;
// Report command completion
if (FAILED(hrCompletionResult))
{
if (FALSE == fSuppressDetails)
StrCpyN(m_szOperationDetails, lpszResponseText, ARRAYSIZE(m_szOperationDetails));
cfeEvent = CFSM_EVENT_ERROR;
}
else
cfeEvent = CFSM_EVENT_OPERATIONCOMPLETE;
m_fTerminating = TRUE; // Either event should make us go to CFSM_STATE_OPERATIONCOMPLETE
m_hrOperationResult = hrCompletionResult;
// Check for user-induced connection drop, replace with non-UI error code
if (IXP_E_CONNECTION_DROPPED == hrCompletionResult && m_fDisconnecting)
m_hrOperationResult = STORE_E_OPERATION_CANCELED;
hrTemp = _ConnFSM_QueueEvent(cfeEvent);
TraceError(hrTemp);
// Might not want to do anything past this point, we might be all released
}
else if (CFSM_STATE_WAITFOROPERATIONDONE == m_cfsState)
{
// *** TEMPORARY until we remove CIMAPSync queueing code
do {
hrTemp = _SendNextOperation(NOFLAGS);
TraceError(hrTemp);
} while (S_OK == hrTemp);
}
return S_OK;
} // _OnCmdComplete
//***************************************************************************
// Function: DownloadFoldersSequencer
//
// Purpose:
// This function is a helper function for CmdCompletionNotification. I
// created it because the former function was getting big and unwieldy. I
// probably shouldn't have bothered, but now I'm too lazy to put it back.
// Besides, the comments for this function are going to be HUGE.
// This function contains all of the operations involved in a folder
// hierarchy download. In addition to the actual hierarchy download, this
// includes Root Folder Path (or Prefix) creation, and hierarchy character
// determination (often abbreviated HCF, where "F" is for Finding).
//
// Details: See the end of the module, where many details are provided.
//
// Arguments:
// WPARAM tid [in] - the wParam associated with this operation.
// LPARAM lParam [in] - the lParam associated with this operation, if any.
// HRESULT hrCompletionResult [in] - HRESULT indicating success or failure
// of the IMAP command.
// LPSTR lpszResponseText [in] - response text associated with the tagged
// response from the IMAP server.
// LPBOOL pfCompletion [out] - set to TRUE if current operation is finished.
//
// Returns:
// HRESULT indicating success or failure. This return value should be
// assigned to hrCompletionResult so that errors are displayed and the
// dialog taken down.
//***************************************************************************
HRESULT CIMAPSync::DownloadFoldersSequencer(const WPARAM wpTransactionID,
const LPARAM lParam,
HRESULT hrCompletionResult,
const LPCSTR lpszResponseText,
LPBOOL pfCompletion)
{
HRESULT hrTemp;
TraceCall("CIMAPSync::CIMAPFolderMgr::DownloadFoldersSequencer");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != lpszResponseText);
IxpAssert(SOT_SYNCING_STORE == m_sotCurrent || SOT_PUT_MESSAGE == m_sotCurrent);
IxpAssert(NULL != pfCompletion);
IxpAssert(FALSE == *pfCompletion);
// Initialize variables
m_szOperationProblem[0] = '\0';
// Take action on the completion of certain commands
switch (wpTransactionID)
{
case tidPREFIXLIST:
AssertSz('\0' != m_szRootFolderPrefix[0], "You tried to list a blank folder. Brilliant.");
if (SUCCEEDED(hrCompletionResult))
{
// If we're looking for root-lvl hierarchy char, maybe this listing will help
if (NULL != m_phcfHierarchyCharInfo)
FindRootHierarchyChar(fHCF_PLAN_A_ONLY, lParam);
if (INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar)
{
AssertSz(FALSE == m_fPrefixExists, "This doesn't make sense. Where's my HC?");
// List top level of hierarchy for hierarchy char determination
hrCompletionResult = _EnqueueOperation(tidPREFIX_HC, lParam,
icLIST_COMMAND, g_szPercent, uiNORMAL_PRIORITY);
TraceError(hrCompletionResult);
}
else
{
// We don't need to find HC - list the prefixed hierarchy or create prefix
if (m_fPrefixExists)
{
char szBuf[CCHMAX_IMAPFOLDERPATH+3];
// Prefix exists, so list it (only fixed buffers, so limited overflow risk)
wnsprintf(szBuf, ARRAYSIZE(szBuf), "%.512s%c*", m_szRootFolderPrefix, m_cRootHierarchyChar);
hrCompletionResult = _EnqueueOperation(tidFOLDERLIST, lParam,
icLIST_COMMAND, szBuf, uiNORMAL_PRIORITY);
TraceError(hrCompletionResult);
}
else
{
// Prefix doesn't exist, better create it
hrCompletionResult = CreatePrefix(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
TraceError(hrCompletionResult);
}
}
}
else
{
// Inform the user of the error.
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidPREFIXLIST
case tidPREFIX_HC:
if (SUCCEEDED(hrCompletionResult))
{
// If we're looking for root-lvl hierarchy char, maybe this listing will help
AssertSz(NULL != m_phcfHierarchyCharInfo, "Why LIST % if you already KNOW HC?")
FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
// If Plan A for LIST % was sufficient to find HC, create prefix
if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
{
hrCompletionResult = CreatePrefix(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
TraceError(hrCompletionResult);
}
// else - Plan B has already been launched. Wait for its completion.
}
else
{
// Inform the user of the error.
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidPREFIX_HC
case tidHIERARCHYCHAR_LIST_B:
// I don't care if this succeeded or failed. FindRootHierarchyChar will launch Plan C,
// if necessary. Suppress error-reporting due to failed tidHIERARCHYCHAR_LIST_B
IxpAssert(NULL != m_phcfHierarchyCharInfo);
IxpAssert(hcfPLAN_B == m_phcfHierarchyCharInfo->hcfStage);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
hrCompletionResult = S_OK; // Suppress error-reporting - don't take down dialog
else
break;
}
FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
// If we found the hierarchy char, proceed with prefix OR special folder creation
if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
{
hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
TraceError(hrCompletionResult);
}
// else - Plan C has already been launched. Wait for its completion
break; // case tidHIERARCHYCHAR_LIST_B
case tidHIERARCHYCHAR_CREATE:
IxpAssert(NULL != m_phcfHierarchyCharInfo);
IxpAssert(hcfPLAN_C == m_phcfHierarchyCharInfo->hcfStage);
if (SUCCEEDED(hrCompletionResult))
{
// Try to list the folder for its juicy hierarchy char
hrCompletionResult = _EnqueueOperation(tidHIERARCHYCHAR_LIST_C, lParam,
icLIST_COMMAND, m_phcfHierarchyCharInfo->szTempFldrName,
uiNORMAL_PRIORITY);
TraceError(hrCompletionResult);
}
else if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
{
// Try the next plan in the list (which should succeed), and create prefix/special fldrs
TraceResult(hrCompletionResult);
FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
AssertSz(NULL == m_phcfHierarchyCharInfo,
"HEY, you added a new hierarchy char search plan and you didn't TELL ME!?");
hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
TraceError(hrCompletionResult);
}
break; // case tidHIERARCHYCHAR_CREATE
case tidHIERARCHYCHAR_LIST_C:
// I don't care if this succeeded or failed. Defer check for hierarchy
// char, we MUST delete the temp fldr, for now
IxpAssert(NULL != m_phcfHierarchyCharInfo);
IxpAssert(hcfPLAN_C == m_phcfHierarchyCharInfo->hcfStage);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
hrCompletionResult = S_OK; // Suppress default error-handling - don't take down dialog
else
break;
}
hrCompletionResult = _EnqueueOperation(tidHIERARCHYCHAR_DELETE, lParam,
icDELETE_COMMAND, m_phcfHierarchyCharInfo->szTempFldrName, uiNORMAL_PRIORITY);
TraceError(hrCompletionResult);
break; // case tidHIERARCHYCHAR_LIST_C
case tidHIERARCHYCHAR_DELETE:
if (FAILED(hrCompletionResult))
{
TraceError(hrCompletionResult);
if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
hrCompletionResult = S_OK; // Suppress error
else
break;
}
// Look for hierarchy char - doesn't matter if delete failed, or not
FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
AssertSz(NULL == m_phcfHierarchyCharInfo,
"HEY, you added a new hierarchy char search plan and you didn't TELL ME!?");
// Proceed with prefix/special folder creation (I assume I've found the hierarchy char)
AssertSz(INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar,
"By this stage, I should have a HC - an assumed one, if necessary.");
hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem),
lParam, pfCompletion);
TraceError(hrCompletionResult);
break; // case tidHIERARCHYCHAR_DELETE
case tidFOLDERLIST:
if (SUCCEEDED(hrCompletionResult))
{
char szBuf[CCHMAX_IMAPFOLDERPATH+3];
// Launch LSUB * or LSUB <prefix>/* as appropriate
if ('\0' != m_szRootFolderPrefix[0])
// Construct prefix + * (only fixed buffers, so limited overflow risk)
wnsprintf(szBuf, ARRAYSIZE(szBuf), "%.512s%c*", m_szRootFolderPrefix, m_cRootHierarchyChar);
else
{
szBuf[0] = '*';
szBuf[1] = '\0';
}
hrCompletionResult = _EnqueueOperation(tidFOLDERLSUB, lParam,
icLSUB_COMMAND, szBuf, uiNORMAL_PRIORITY);
TraceError(hrCompletionResult);
}
else
{
// Inform the user of the error.
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidFOLDERLIST
case tidPREFIX_CREATE:
if (FAILED(hrCompletionResult))
{
char szFmt[2*CCHMAX_STRINGRES];
// Inform the user of the error.
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPPrefixCreateFailedFmt, szFmt, ARRAYSIZE(szFmt));
wnsprintf(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), szFmt, m_szRootFolderPrefix);
break;
}
// Check if we need to create special folders
m_fPrefixExists = TRUE; // Make sure PostHCD creates special fldrs instead of the prefix
hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
if (FAILED(hrCompletionResult) || FALSE == *pfCompletion)
{
// We're not ready to sync deleted folders just yet: special folders are being created
break;
}
// If we reached this point, tidPREFIX_CREATE was successful:
// Prefix was created. No need to list its hierarchy (it has none),
// and we'll assume it can take Inferiors. We're DONE!
// *** FALL THROUGH to tidFOLDERLSUB, to sync deleted folders ***
case tidFOLDERLSUB:
if (SUCCEEDED(hrCompletionResult))
{
if (NULL != m_phcfHierarchyCharInfo)
FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
{
if (m_fCreateSpecial)
{
// We now have the hierarchy character (required to create special folders).
// Create special folders
hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
break;
}
}
else
{
EndFolderList();
// Close the download folders dialog, IF we've found the hierarchy char
// If HC not found, Plan B has already been launched, so wait for its completion
if (FOLDERID_INVALID == (FOLDERID)lParam)
{
Assert(INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar);
*pfCompletion = TRUE;
}
}
} // if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
} // if (SUCCEEDED(hrCompletionResult))
else
{
// Inform the user of the error.
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidFOLDERLSUB
case tidSPECIALFLDRSUBSCRIBE:
// Regardless of success/failure, subscribe this special folder locally!
hrTemp = m_pStore->SubscribeToFolder(((CREATE_FOLDER_INFO *)lParam)->idFolder,
fSUBSCRIBE, NOSTORECALLBACK);
TraceError(hrTemp);
hrCompletionResult = S_OK; // Suppress error
// *** FALL THROUGH ***
case tidSPECIALFLDRLIST:
case tidSPECIALFLDRLSUB:
if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
hrCompletionResult = CreateNextSpecialFolder((CREATE_FOLDER_INFO *)lParam, pfCompletion);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsCreateSpecialFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
break; // case tidSPECIALFLDRLIST, tidSPECIALFLDRLSUB, tidSPECIALFLDRSUBSCRIBE
default:
AssertSz(FALSE, "Hey, why is DownloadFoldersSequencer getting called?");
break; // default case
}; // switch (wpTransactionID)
if (FAILED(hrCompletionResult))
{
*pfCompletion = TRUE;
DisposeOfWParamLParam(wpTransactionID, lParam, hrCompletionResult);
}
return hrCompletionResult;
} // DownloadFoldersSequencer
// Details for DownloadFoldersSequencer (1/16/97, raych)
// ------------------------------------
// DownloadFoldersSequencer implements a somewhat complicated flow of execution.
// For a map of the execution flow, you can either create one from the function,
// or look on pp. 658-659 of my logbook. In any case, you can basically divide
// the execution flow into two categories, one for a prefixed account (ie, one
// with a Root Folder Path), and the flow for a non-prefixed account.
//
// (12/02/1998): This code is getting unmaintainable, but previous attempts to
// clean it up failed due to insufficient time. If you get the chance to re-write
// then please do. The process is greatly simplified if we assume IMAP4rev1 servers
// because then hierarchy character determination becomes a straightforward matter.
//
// For a non-prefixed account, the longest possible path is:
// 1) tidFOLDERLSUB (LIST *), syncs deleted msgs
// 2) tidHIERARCHYCHAR_LIST_B 3) tidHIERARCHYCHAR_CREATE
// 4) tidHIERARCHYCHAR_LIST_C 5) tidHIERARCHYCHAR_DELETE
// 6) Special Folder Creation (END).
//
// For a prefixed account, where the prefix already exists:
// 1) tidPREFIXLIST - this WILL discover HC
// 2) tidFOLDERLSUB (LIST <PREFIX><HC>*), syncs deleted msgs
// 3) Special Folder Creation (END).
//
// For a prefixed account, where the prefix does not exist:
// 1) tidPREFIXLIST
// 2) tidPREFIX_HC 3) tidHIERARCHYCHAR_LIST_B
// 4) tidHIERARCHYCHAR_CREATE 5) tidHIERARCHYCHAR_LIST_C
// 6) tidHIERARCHYCHAR_DELETE 7) tidPREFIX_CREATE, syncs deleted msgs
// 8) Special Folder Creation (END).
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::PostHCD(LPSTR pszErrorDescription,
DWORD dwSizeOfErrorDescription,
LPARAM lParam, LPBOOL pfCompletion)
{
HRESULT hrResult;
CREATE_FOLDER_INFO *pcfiCreateInfo;
// First, we try to create the prefix
hrResult = CreatePrefix(pszErrorDescription, dwSizeOfErrorDescription,
lParam, pfCompletion);
if (FAILED(hrResult) || (SUCCEEDED(hrResult) && FALSE == *pfCompletion))
{
// Either we successfully launched tidPREFIX_CREATE or something failed.
// Return as if caller had called CreatePrefix directly.
goto exit;
}
// At this point, CreatePrefix has told us that we do not need to create a prefix
Assert(TRUE == *pfCompletion);
Assert(SUCCEEDED(hrResult));
// Start special folder creation
pcfiCreateInfo = new CREATE_FOLDER_INFO;
if (NULL == pcfiCreateInfo)
{
hrResult = TraceResult(E_OUTOFMEMORY);
goto exit;
}
pcfiCreateInfo->pszFullFolderPath = NULL;
pcfiCreateInfo->idFolder = FOLDERID_INVALID;
pcfiCreateInfo->dwFlags = 0;
pcfiCreateInfo->csfCurrentStage = CSF_INIT;
pcfiCreateInfo->dwCurrentSfType = FOLDER_INBOX;
pcfiCreateInfo->dwFinalSfType = FOLDER_MAX - 1;
pcfiCreateInfo->lParam = (LPARAM) FOLDERID_INVALID;
pcfiCreateInfo->pcoNextOp = PCO_NONE;
hrResult = CreateNextSpecialFolder(pcfiCreateInfo, pfCompletion);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
m_fCreateSpecial = FALSE;
exit:
return hrResult;
}
//***************************************************************************
// Function: CreatePrefix
//
// Purpose:
// This function is called after the hierarchy character is found. If the
// user specified a prefix, this function creates it. Otherwise, it takes
// down the dialog box (since HC discovery is the last step for non-prefixed
// accounts).
//
// Arguments:
// LPSTR pszErrorDescription [out] - if an error is encountered, this
// function deposits a description into this output buffer.
// DWORD dwSizeOfErrorDescription [in] - size of pszErrorDescription.
// LPARAM lParam [in] - lParam to issue with IMAP command.
//
// Returns:
// HRESULT indicating success or failure. This return value should be
// assigned to hrCompletionResult so that errors are displayed and the
// dialog taken down.
//***************************************************************************
HRESULT CIMAPSync::CreatePrefix(LPSTR pszErrorDescription,
DWORD dwSizeOfErrorDescription,
LPARAM lParam, LPBOOL pfCompletion)
{
char szBuf[CCHMAX_IMAPFOLDERPATH+2];
HRESULT hr = S_OK;
TraceCall("CIMAPSync::CreatePrefix");
IxpAssert(m_cRef > 0);
AssertSz(INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar,
"How do you intend to create a prefix when you don't know HC?");
IxpAssert(NULL != pfCompletion);
IxpAssert(FALSE == *pfCompletion);
// Check if there IS a prefix to create
if ('\0' == m_szRootFolderPrefix[0] || m_fPrefixExists)
{
// No prefix to create. We are done: we've discovered the hierarchy character
*pfCompletion = TRUE;
goto exit;
}
// Create the prefix
if ('\0' != m_cRootHierarchyChar)
{
wnsprintf(szBuf, ARRAYSIZE(szBuf), "%.512s%c", m_szRootFolderPrefix, m_cRootHierarchyChar);
hr = _EnqueueOperation(tidPREFIX_CREATE, lParam, icCREATE_COMMAND,
szBuf, uiNORMAL_PRIORITY);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
else
{
// We have a prefix on a non-hierarchical IMAP server!
LoadString(g_hLocRes, idsIMAPNoHierarchyLosePrefix, pszErrorDescription, dwSizeOfErrorDescription);
hr = TraceResult(hrIMAP_E_NoHierarchy);
goto exit;
}
exit:
return hr;
}
void CIMAPSync::EndFolderList(void)
{
HRESULT hrTemp;
// Folder DL complete: Delete any folders in foldercache which weren't LISTed
// and unsubscribe any folders which weren't LSUBed
if (NULL != m_pCurrentHash)
{
hrTemp = DeleteHashedFolders(m_pCurrentHash);
TraceError(hrTemp);
}
if (NULL != m_pListHash)
{
hrTemp = UnsubscribeHashedFolders(m_pStore, m_pListHash);
TraceError(hrTemp);
}
}
//***************************************************************************
// Function: RenameSequencer
//
// Purpose:
// This function is a helper function for CmdCompletionNotification. It
// contains all of the sequencing operations required to perform a folder
// rename. For details, see the end of the function.
//
// Arguments:
// Same as for CmdCompletionNotification.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::RenameSequencer(const WPARAM wpTransactionID,
const LPARAM lParam,
HRESULT hrCompletionResult,
LPCSTR lpszResponseText,
LPBOOL pfDone)
{
CRenameFolderInfo *pRenameInfo;
BOOL fRenameDone;
TraceCall("CIMAPSync::RenameSequencer");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != lpszResponseText);
// Initialize variables
pRenameInfo = (CRenameFolderInfo *) lParam;
fRenameDone = FALSE;
*pfDone = FALSE;
// Take action on the completion of certain commands
switch (wpTransactionID)
{
case tidRENAME:
if (SUCCEEDED(hrCompletionResult))
{
// Update the foldercache (ignore errors, it reports them itself)
// Besides, user can hopefully fix foldercache errors by refreshing folderlist
// Assume server did a hierarchical rename: if not, we fix
hrCompletionResult = m_pStore->RenameFolder(pRenameInfo->idRenameFolder,
ImapUtil_ExtractLeafName(pRenameInfo->pszNewFolderPath,
pRenameInfo->cHierarchyChar), NOFLAGS, NOSTORECALLBACK);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
lpszResponseText = c_szEmpty; // This isn't applicable anymore
LoadString(g_hLocRes, idsIMAPRenameFCUpdateFailure, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
fRenameDone = TRUE; // This puts a damper on things, yes?
break;
}
// Subscribe the renamed tree
hrCompletionResult = RenameTreeTraversal(tidRENAMESUBSCRIBE,
pRenameInfo, fINCLUDE_RENAME_FOLDER);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
lpszResponseText = c_szEmpty; // This isn't applicable anymore
LoadString(g_hLocRes, idsIMAPRenameSubscribeFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
fRenameDone = TRUE;
break;
}
// List the old tree to see if it still exists (exclude renamed fldr)
hrCompletionResult = RenameTreeTraversal(tidRENAMELIST,
pRenameInfo, fEXCLUDE_RENAME_FOLDER);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
lpszResponseText = c_szEmpty; // This isn't applicable anymore
// Let's reuse the string for folder list failure
LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
// Arm the trigger for Phase Two launch
pRenameInfo->fPhaseOneSent = TRUE;
} // if (SUCCEEDED(hrCompletionResult))
else
{
// Inform the user of any errors
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPRenameFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
fRenameDone = TRUE;
}
break; // case tidRENAME
case tidRENAMESUBSCRIBE:
// Count the number of failed subscriptions
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
pRenameInfo->iNumFailedSubs += 1;
if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
hrCompletionResult = S_OK; // Suppress failure report
}
lpszResponseText = c_szEmpty; // This isn't applicable anymore
// Decrement subscribe response counter, Watch for phase 2 launch condition
pRenameInfo->iNumSubscribeRespExpected -= 1;
if (0 == pRenameInfo->iNumSubscribeRespExpected)
{
HRESULT hrTemp;
// Theoretically, all subfolders of renamed folder are now subscribed
hrTemp = SubscribeSubtree(pRenameInfo->idRenameFolder, fSUBSCRIBE);
TraceError(hrTemp);
}
if (EndOfRenameFolderPhaseOne(pRenameInfo) && SUCCEEDED(hrCompletionResult))
{
// It is time to start the next phase of the operation
hrCompletionResult = RenameFolderPhaseTwo(pRenameInfo, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
TraceError(hrCompletionResult);
}
break; // case tidRENAMESUBSCRIBE
case tidRENAMELIST:
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
hrCompletionResult = S_OK; // Suppress failure report
}
lpszResponseText = c_szEmpty; // This isn't applicable anymore
// Count the number of list responses returned. Watch for phase 2 launch condition
pRenameInfo->iNumListRespExpected -= 1;
if (EndOfRenameFolderPhaseOne(pRenameInfo) && SUCCEEDED(hrCompletionResult))
{
// It is time to start the next phase of the operation
hrCompletionResult = RenameFolderPhaseTwo(pRenameInfo, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
TraceError(hrCompletionResult);
}
break; // case tidRENAMELIST
case tidRENAMERENAME:
// Failure will not be tolerated
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPAtomicRenameFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
}
lpszResponseText = c_szEmpty; // This isn't applicable anymore
// Decrement the (second) rename counts, watch for phase 2 launch condition
pRenameInfo->iNumRenameRespExpected -= 1;
if (EndOfRenameFolderPhaseTwo(pRenameInfo))
fRenameDone = TRUE;
break; // tidRENAMERENAME
case tidRENAMESUBSCRIBE_AGAIN:
// Modify the number of failed subscriptions based on success
if (SUCCEEDED(hrCompletionResult))
pRenameInfo->iNumFailedSubs -= 1;
else
pRenameInfo->iNumFailedSubs += 1;
hrCompletionResult = S_OK; // Suppress failure report
lpszResponseText = c_szEmpty; // This isn't applicable anymore
// Count the number of subscribe responses returned, watch for end-of-operation
pRenameInfo->iNumSubscribeRespExpected -= 1;
if (0 == pRenameInfo->iNumSubscribeRespExpected)
{
// Theoretically, all subfolders of renamed folder are now subscribed
hrCompletionResult = SubscribeSubtree(pRenameInfo->idRenameFolder, fSUBSCRIBE);
TraceError(hrCompletionResult);
}
if (EndOfRenameFolderPhaseTwo(pRenameInfo))
fRenameDone = TRUE;
break; // case tidRENAMESUBSCRIBE_AGAIN
case tidRENAMEUNSUBSCRIBE:
// Count the number of failed unsubscribe's, to report to user at end-of-operation
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
pRenameInfo->iNumFailedUnsubs += 1;
}
hrCompletionResult = S_OK; // Suppress failure report
lpszResponseText = c_szEmpty; // This isn't applicable anymore
// Count the number of unsubscribe responses returned, watch for end-of-operation
pRenameInfo->iNumUnsubscribeRespExpected -= 1;
if (EndOfRenameFolderPhaseTwo(pRenameInfo))
fRenameDone = TRUE;
break; // case tidRENAMEUNSUBSCRIBE
default:
AssertSz(FALSE, "This is not an understood rename operation.");
break; // default case
} // switch (wpTransactionID)
// That's one less rename command pending from the server
pRenameInfo->Release();
*pfDone = fRenameDone;
return hrCompletionResult;
} // RenameSequencer
// Details for RenameSequencer (2/4/97, raych)
// ---------------------------
// A rename operation includes the original rename, subscription tracking, and
// atomic rename simulation (for Cyrus servers). To perform this, the rename
// operation is divided into two phases:
//
// PHASE ONE:
// 1) Assume rename was atomic. Subscribe new (renamed) folder hierarchy.
// 2) List first child of old rename folder, to check if rename was in fact atomic.
//
// PHASE TWO:
// 1) If rename was not atomic, issue a RENAME for each child of rename folder
// in order to SIMULATE an atomic rename. This does not check for collisions
// in the renamed space.
// 2) If the rename was not atomic, try to subscribe the new (renamed) folder
// hierarchy, again.
// 3) Unsubscribe the old folder hierarchy.
//
// What a pain.
//***************************************************************************
// Function: EndOfRenameFolderPhaseOne
//
// Purpose:
// This function detects whether Phase One of the rename operation has
// completed.
//
// Arguments:
// CRenameFolderInfo *pRenameInfo [in] - the CRenameFolderInfo associated
// with the RENAME operation.
//
// Returns:
// TRUE if Phase One has ended, otherwise FALSE. Phase One cannot end
// if it has not been sent, yet.
//***************************************************************************
inline BOOL CIMAPSync::EndOfRenameFolderPhaseOne(CRenameFolderInfo *pRenameInfo)
{
if (pRenameInfo->fPhaseOneSent &&
pRenameInfo->iNumSubscribeRespExpected <= 0 &&
pRenameInfo->iNumListRespExpected <= 0)
{
IxpAssert(0 == pRenameInfo->iNumSubscribeRespExpected);
IxpAssert(0 == pRenameInfo->iNumListRespExpected);
return TRUE; // This marks the end of phase one
}
else
return FALSE;
} // EndOfRenameFolderPhaseOne
//***************************************************************************
// Function: EndOfRenameFolderPhaseTwo
//
// Purpose:
// This function detects whether Phase Two of the rename operation has
// completed.
//
// Arguments:
// CRenameFolderInfo *pRenameInfo [in] - the CRenameFolderInfo associated
// with the RENAME operation.
//
// Returns:
// TRUE if Phase Two has ended, otherwise FALSE. Phase Two cannot end
// if it has not been sent, yet.
//***************************************************************************
inline BOOL CIMAPSync::EndOfRenameFolderPhaseTwo(CRenameFolderInfo *pRenameInfo)
{
if (pRenameInfo->fPhaseTwoSent &&
pRenameInfo->iNumRenameRespExpected <= 0 &&
pRenameInfo->iNumSubscribeRespExpected <= 0 &&
pRenameInfo->iNumUnsubscribeRespExpected <= 0)
{
IxpAssert(0 == pRenameInfo->iNumRenameRespExpected);
IxpAssert(0 == pRenameInfo->iNumSubscribeRespExpected);
IxpAssert(0 == pRenameInfo->iNumUnsubscribeRespExpected);
return TRUE; // This marks the end of phase two
}
else
return FALSE;
} // EndOfRenameFolderPhaseTwo
//***************************************************************************
// Function: RenameFolderPhaseTwo
//
// Purpose:
// This function launches Phase Two of the RENAME operation.
//
// Arguments:
// CRenameFolderInfo *pRenameInfo [in] - the CRenameFolderInfo associated
// with the RENAME operation.
// LPSTR szErrorDescription [in] - if an error occurs, this function
// deposits a desription in this buffer.
// DWORD dwSizeOfErrorDescription [in] - size of szErrorDescription.
//
// Returns:
// HRESULT indicating success or failure.
//***************************************************************************
HRESULT CIMAPSync::RenameFolderPhaseTwo(CRenameFolderInfo *pRenameInfo,
LPSTR szErrorDescription,
DWORD dwSizeOfErrorDescription)
{
HRESULT hrCompletionResult;
// Rename subfolders, re-attempt subscription of renamed tree, exclude rename folder
if (pRenameInfo->fNonAtomicRename)
{
hrCompletionResult = RenameTreeTraversal(tidRENAMERENAME,
pRenameInfo, fEXCLUDE_RENAME_FOLDER);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPAtomicRenameFailed, szErrorDescription,
dwSizeOfErrorDescription);
goto exit;
}
hrCompletionResult = RenameTreeTraversal(tidRENAMESUBSCRIBE_AGAIN,
pRenameInfo, fEXCLUDE_RENAME_FOLDER);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPRenameSubscribeFailed, szErrorDescription,
dwSizeOfErrorDescription);
goto exit;
}
}
// Unsubscribe from the old tree, include rename folder
hrCompletionResult = RenameTreeTraversal(tidRENAMEUNSUBSCRIBE,
pRenameInfo, fINCLUDE_RENAME_FOLDER);
if (FAILED(hrCompletionResult))
{
TraceResult(hrCompletionResult);
LoadString(g_hLocRes, idsIMAPRenameUnsubscribeFailed, szErrorDescription,
dwSizeOfErrorDescription);
goto exit;
}
// Arm the trigger for end-of-operation launch
pRenameInfo->fPhaseTwoSent = TRUE;
exit:
return hrCompletionResult;
} // RenameFolderPhaseTwo
//***************************************************************************
// Function: _OnMailBoxList
// Description: Helper function for OnResponse.
//
// This function saves the information from the LIST/LSUB command to the
// folder cache. If the folder already exists in the folder cache, its
// mailbox flags are updated. If it does not exist, a handle (and message
// cache filename) are reserved for the folder, and it is entered into
// the folder cache. This function is also part of the hierarchy character
// determination code. If a hierarchy character is encountered during a
// folder hierarchy download, we assume this is the hierarchy character
// for root-level folders.
//
// Arguments:
// WPARAM wpTransactionID [in] - the wParam of this operation (eg,
// tidFOLDERLSUB or tidCREATELIST).
// LPARAM lParam [in] - the lParam of this operation.
// LPSTR pszMailboxName [in] - the mailbox name returned via the LIST
// response, eg "INBOX".
// IMAP_MBOXFLAGS imfMboxFlags [in] - the mailbox flags returned via the
// LIST response, eg "\NoSelect".
// char cHierarchyChar [in] - the hierarchy character returned via the
// LIST response, eg '/'.
//***************************************************************************
HRESULT CIMAPSync::_OnMailBoxList( WPARAM tid,
LPARAM lParam,
LPSTR pszMailboxName,
IMAP_MBOXFLAGS imfMboxFlags,
char cHierarchyChar,
BOOL fNoTranslation)
{
const DWORD dwProgressInterval = 1;
HRESULT hr = S_OK;
FOLDERID idNewFolder = FOLDERID_INVALID;
FOLDERID idTemp = FOLDERID_INVALID;
BOOL fHandledLPARAM = FALSE;
LPSTR pszLocalPath = NULL;
BOOL fValidPrefix;
SPECIALFOLDER sfType;
BOOL fFreeLocalPath = FALSE;
TraceCall("CIMAPSync::_OnMailBoxList");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != pszMailboxName);
// Hierarchy-character determination code
if (NULL != m_phcfHierarchyCharInfo)
{
switch (cHierarchyChar)
{
case '\0':
// If our prefix is not INBOX, we MUST treat NIL as a valid hierarchy char
if (tidPREFIXLIST == tid ||
0 != lstrcmpi(pszMailboxName, c_szInbox))
m_phcfHierarchyCharInfo->fNonInboxNIL_Seen = TRUE;
break;
case '.':
m_phcfHierarchyCharInfo->fDotHierarchyCharSeen = TRUE;
break;
default:
// Set the bit in the array which corresponds to this character
m_phcfHierarchyCharInfo->bHierarchyCharBitArray[cHierarchyChar/8] |=
(1 << cHierarchyChar%8);
break;
}
}
// Remove prefix from full folder path
pszLocalPath = RemovePrefixFromPath(m_szRootFolderPrefix, pszMailboxName,
cHierarchyChar, &fValidPrefix, &sfType);
// Replace leading INBOX with localized folder name
const int c_iLenOfINBOX = 5; // Let me know if this changes
Assert(lstrlen(c_szINBOX) == c_iLenOfINBOX);
if (0 == StrCmpNI(pszLocalPath, c_szINBOX, c_iLenOfINBOX))
{
char cNextChar;
cNextChar = pszLocalPath[c_iLenOfINBOX];
if ('\0' == cNextChar || cHierarchyChar == cNextChar)
{
BOOL fResult;
int iLocalizedINBOXLen;
int cchNewPathLen;
char szInbox[CCHMAX_STRINGRES];
LPSTR pszNew;
// We found INBOX or INBOX<HC>: replace INBOX with localized version
Assert(FOLDER_INBOX == sfType || '\0' != cNextChar);
iLocalizedINBOXLen = LoadString(g_hLocRes, idsInbox, szInbox, ARRAYSIZE(szInbox));
cchNewPathLen = iLocalizedINBOXLen + lstrlen(pszLocalPath + c_iLenOfINBOX) + 1;
fResult = MemAlloc((void **)&pszNew, cchNewPathLen * sizeof(pszNew[0]));
if (FALSE == fResult)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
StrCpyN(pszNew, szInbox, cchNewPathLen);
StrCatBuff(pszNew, (pszLocalPath + c_iLenOfINBOX), cchNewPathLen);
pszLocalPath = pszNew;
fFreeLocalPath = TRUE;
}
}
// Add folder to foldercache if current operation warrants it (LIST only, ignore LSUB)
switch (tid)
{
case tidSPECIALFLDRLIST:
case tidFOLDERLIST:
case tidCREATELIST:
if (fValidPrefix && pszLocalPath[0] != '\0')
{
DWORD dwAFTCFlags;
dwAFTCFlags = (fNoTranslation ? AFTC_NOTRANSLATION : 0);
hr = AddFolderToCache(pszLocalPath, imfMboxFlags, cHierarchyChar,
dwAFTCFlags, &idNewFolder, sfType);
if (FAILED(hr))
{
TraceResult(hr);
goto exit;
}
}
}
// Tie up loose ends and exit
switch (tid)
{
// Are we looking for a prefix listing?
case tidPREFIXLIST:
IxpAssert(0 == lstrcmpi(pszMailboxName, m_szRootFolderPrefix));
m_fPrefixExists = TRUE;
fHandledLPARAM = TRUE;
goto exit; // Skip addition to foldercache
case tidRENAMELIST:
if (NULL != lParam)
{
// Well, looks like we have some subfolders to rename
((CRenameFolderInfo *)lParam)->fNonAtomicRename = TRUE;
fHandledLPARAM = TRUE;
}
break;
case tidSPECIALFLDRLIST:
case tidFOLDERLIST:
case tidCREATELIST:
fHandledLPARAM = TRUE;
// Only act on validly prefixed folders
if (fValidPrefix && NULL != m_pCurrentHash)
{
// Remove LISTed folder from m_pCurrentHash (list of cached folders)
hr = m_pCurrentHash->Find(pszLocalPath, fREMOVE, (void **)&idTemp);
if (FAILED(hr))
{
if (FOLDERID_INVALID != idNewFolder)
idTemp = idNewFolder;
else
idTemp = FOLDERID_INVALID;
}
// NOTE that it is possible for idTemp != idNewFolder. This occurs if
// I change RFP from "" to "aaa" and there exists two folders, "bbb" and
// "aaa/bbb". Believe it or not, this happened to me during rudimentary testing.
// The correct folder to use in this case is idTemp, which is determined using FULL path
// Record all LISTed folders in m_pListHash
if (NULL != m_pListHash)
{
hr = m_pListHash->Insert(pszLocalPath, idTemp, HF_NO_DUPLICATES);
TraceError(hr);
}
}
if (tidCREATELIST != tid && FALSE == (tidSPECIALFLDRLIST == tid && NULL != lParam &&
0 == lstrcmpi(pszMailboxName, ((CREATE_FOLDER_INFO *)lParam)->pszFullFolderPath)))
break;
// *** FALL THROUGH if tidCREATELIST, or tidSPECIALFLDRLIST and exact path match ***
if (NULL != lParam)
{
CREATE_FOLDER_INFO *pcfi = (CREATE_FOLDER_INFO *) lParam;
// Inform cmd completion that it's OK to send the subscribe cmd
// Also record fldrID of new fldr so we can update store after successful subscribe
pcfi->dwFlags |= CFI_RECEIVEDLISTING;
pcfi->idFolder = idNewFolder;
fHandledLPARAM = TRUE;
}
break;
case tidSPECIALFLDRLSUB:
case tidFOLDERLSUB:
// Verify that we already received this folderpath via a LIST response
// If we DID receive this folder via LIST, remove from m_pListHash
fHandledLPARAM = TRUE;
// Only act on validly prefixed folders
if (fValidPrefix)
{
hr = m_pListHash->Find(pszLocalPath, fREMOVE, (void **)&idTemp);
if (SUCCEEDED(hr))
{
// This folder was received via LIST and thus it exists: subscribe it
if (FOLDERID_INVALID != idTemp)
{
hr = m_pStore->SubscribeToFolder(idTemp, fSUBSCRIBE, NOSTORECALLBACK);
TraceError(hr);
}
}
else
{
DWORD dwTranslateFlags;
HRESULT hrTemp;
// This folder was not returned via LIST. Destroy it
hrTemp = FindHierarchicalFolderName(pszLocalPath, cHierarchyChar,
&idTemp, pahfoDONT_CREATE_FOLDER);
if (SUCCEEDED(hrTemp))
{
// Do record result of this in hr because failure here is not cool
hr = DeleteFolderFromCache(idTemp, fNON_RECURSIVE);
TraceError(hr);
}
// if FAILED(hr), we probably never cached this folder, so ignore it
// Unsubscribe it regardless of whether it was in the foldercache
// If this folder is fNoTranslation, we have to disable translation for this
// call to UNSUBSCRIBE. Otherwise IIMAPTransport2 should already have translation enabled
hrTemp = S_OK;
if (fNoTranslation)
{
dwTranslateFlags = IMAP_MBOXXLATE_VERBATIMOK | IMAP_MBOXXLATE_RETAINCP |
IMAP_MBOXXLATE_DISABLE;
hrTemp = m_pTransport->SetDefaultCP(dwTranslateFlags, 0);
}
if (SUCCEEDED(hrTemp))
{
hrTemp = _EnqueueOperation(tidDONT_CARE, 0, icUNSUBSCRIBE_COMMAND,
pszMailboxName, uiNORMAL_PRIORITY);
TraceError(hrTemp);
}
// Restore translation mode to default (luckily we always know translation mode
// of a folder list)
if (fNoTranslation)
{
dwTranslateFlags &= ~(IMAP_MBOXXLATE_DISABLE);
dwTranslateFlags |= IMAP_MBOXXLATE_DEFAULT;
hrTemp = m_pTransport->SetDefaultCP(dwTranslateFlags, 0);
}
}
}
if (tidSPECIALFLDRLSUB == tid && NULL != lParam &&
0 == (IMAP_MBOX_NOSELECT & imfMboxFlags))
{
// Inform cmd completion that there's no need to subscribe special folder
if (0 == lstrcmpi(pszMailboxName, ((CREATE_FOLDER_INFO *)lParam)->pszFullFolderPath))
((CREATE_FOLDER_INFO *)lParam)->dwFlags |= CFI_RECEIVEDLISTING;
}
break;
case tidHIERARCHYCHAR_LIST_B:
case tidHIERARCHYCHAR_LIST_C:
case tidPREFIX_HC:
fHandledLPARAM = TRUE;
break;
default:
AssertSz(FALSE, "Unhandled LIST/LSUB operation");
break;
}
// Provide progress indication
if (SOT_SYNCING_STORE == m_sotCurrent && NULL != m_pCurrentCB)
{
// Update progress indication
m_pCurrentCB->OnProgress(m_sotCurrent, ++m_cFolders, 0, m_szAccountName);
}
exit:
IxpAssert(NULL == lParam || fHandledLPARAM || FAILED(hr));
if (fFreeLocalPath)
MemFree(pszLocalPath);
return S_OK;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::_OnAppendProgress(LPARAM lParam, DWORD dwCurrent, DWORD dwTotal)
{
APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) lParam;
TraceCall("CIMAPSync::OnAppendProgress");
IxpAssert(m_cRef > 0);
IxpAssert(NULL != lParam);
IxpAssert(SOT_PUT_MESSAGE == m_sotCurrent);
if (NULL != m_pCurrentCB)
{
HRESULT hrTemp;
hrTemp = m_pCurrentCB->OnProgress(SOT_PUT_MESSAGE, dwCurrent, dwTotal, NULL);
TraceError(hrTemp);
}
return S_OK;
}
//***************************************************************************
//***************************************************************************
HRESULT CIMAPSync::_OnStatusResponse(IMAP_STATUS_RESPONSE *pisrStatusInfo)
{
HRESULT hrResult;
FOLDERID idFolder;
FOLDERINFO fiFolderInfo;
LPSTR pszMailboxName;
BOOL fValidPrefix;
LONG lMsgDelta;
LONG lUnreadDelta;
CHAR szInbox[CCHMAX_STRINGRES];
TraceCall("CIMAPSync::_OnStatusResponse");
IxpAssert(m_cRef > 0);
// Check that we have the data we need
if (NULL == pisrStatusInfo ||
NULL == pisrStatusInfo->pszMailboxName ||
'\0' == pisrStatusInfo->pszMailboxName[0] ||
FALSE == pisrStatusInfo->fMessages ||
FALSE == pisrStatusInfo->fUnseen)
{
hrResult = TraceResult(E_INVALIDARG);
goto exit;
}
// Figure out who this folder is (figure from path rather than module var FOLDERID for now)
// Assume m_cRootHierarchyChar is HC for this mbox, because IMAP doesn't return it
pszMailboxName = RemovePrefixFromPath(m_szRootFolderPrefix, pisrStatusInfo->pszMailboxName,
m_cRootHierarchyChar, &fValidPrefix, NULL);
AssertSz(fValidPrefix, "Foldercache can only select prefixed folders!");
// bobn, QFE, 7/9/99
// If we have the INBOX, we need to get the local name...
if(0 == StrCmpI(pszMailboxName, c_szINBOX))
{
LoadString(g_hLocRes, idsInbox, szInbox, ARRAYSIZE(szInbox));
pszMailboxName = szInbox;
}
hrResult = FindHierarchicalFolderName(pszMailboxName, m_cRootHierarchyChar,
&idFolder, pahfoDONT_CREATE_FOLDER);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
hrResult = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
// Calculate the number of messages and unread added by this STATUS response
Assert(sizeof(DWORD) == sizeof(LONG));
lMsgDelta = ((LONG)pisrStatusInfo->dwMessages) - ((LONG)fiFolderInfo.cMessages);
lUnreadDelta = ((LONG)pisrStatusInfo->dwUnseen) - ((LONG)fiFolderInfo.cUnread);
// If this is INBOX, we might just send a new mail notification
if (FOLDER_INBOX == fiFolderInfo.tySpecial && lUnreadDelta > 0 && NULL != m_pCurrentCB)
{
HRESULT hrTemp;
hrTemp = m_pCurrentCB->OnProgress(SOT_NEW_MAIL_NOTIFICATION, lUnreadDelta, 0, NULL);
TraceError(hrTemp);
}
// Update counts, and update delta so we can un-apply STATUS changes when re-syncing
fiFolderInfo.cMessages = pisrStatusInfo->dwMessages;
fiFolderInfo.cUnread = pisrStatusInfo->dwUnseen;
fiFolderInfo.dwStatusMsgDelta = ((LONG)fiFolderInfo.dwStatusMsgDelta) + lMsgDelta;
fiFolderInfo.dwStatusUnreadDelta = ((LONG)fiFolderInfo.dwStatusUnreadDelta) + lUnreadDelta;
hrResult = m_pStore->UpdateRecord(&fiFolderInfo);
m_pStore->FreeRecord(&fiFolderInfo);
if (FAILED(hrResult))
{
TraceResult(hrResult);
goto exit;
}
exit:
return hrResult;
}
//===========================================================================
// CRenameFolderInfo Class
//===========================================================================
// The CRenameFolderInfo class used to be a structure (much like
// AppendSendInfo). However, the RENAME operation was the first to
// stream IMAP commands without waiting for completion (no sequencing). This
// meant that if any errors occurred while IMAP commands were still in the
// air, the structure had to wait until the last command came back from the
// server. This was most easily done via AddRef/Release. A class was born.
//
// In the event that a send error occurred, this class is responsible for
// issuing the WM_IMAP_RENAMEDONE window message to the caller.
//***************************************************************************
// Function: CRenameFolderInfo (Constructor)
//***************************************************************************
CRenameFolderInfo::CRenameFolderInfo(void)
{
pszFullFolderPath = NULL;
cHierarchyChar = INVALID_HIERARCHY_CHAR;
pszNewFolderPath = NULL;
idRenameFolder = FOLDERID_INVALID;
iNumSubscribeRespExpected = 0;
iNumListRespExpected = 0;
iNumRenameRespExpected = 0;
iNumUnsubscribeRespExpected = 0;
iNumFailedSubs = 0;
iNumFailedUnsubs = 0;
fNonAtomicRename = 0;
pszRenameCmdOldFldrPath = NULL;
fPhaseOneSent = FALSE;
fPhaseTwoSent = FALSE;
hrLastError = S_OK;
pszProblem = NULL;
pszDetails = NULL;
m_lRefCount = 1;
} // CRenameFolderInfo;
//***************************************************************************
// Function: ~CRenameFolderInfo (Destructor)
//***************************************************************************
CRenameFolderInfo::~CRenameFolderInfo(void)
{
IxpAssert(0 == m_lRefCount);
MemFree(pszFullFolderPath);
MemFree(pszNewFolderPath);
MemFree(pszRenameCmdOldFldrPath);
SafeMemFree(pszProblem);
SafeMemFree(pszDetails);
} // ~CRenameFolderInfo
//***************************************************************************
// Function: AddRef (same one that you already know and love)
//***************************************************************************
long CRenameFolderInfo::AddRef(void)
{
IxpAssert(m_lRefCount > 0);
m_lRefCount += 1;
return m_lRefCount;
} // AddRef
//***************************************************************************
// Function: Release (same one that you already know and love)
//***************************************************************************
long CRenameFolderInfo::Release(void)
{
IxpAssert(m_lRefCount > 0);
m_lRefCount -= 1;
if (0 == m_lRefCount) {
delete this;
return 0;
}
else
return m_lRefCount;
} // Release
//***************************************************************************
//***************************************************************************
BOOL CRenameFolderInfo::IsDone(void)
{
if (m_lRefCount > 1)
return FALSE;
else
{
IxpAssert(1 == m_lRefCount);
return TRUE;
}
}
//***************************************************************************
//***************************************************************************
HRESULT CRenameFolderInfo::SetError(HRESULT hrResult, LPSTR pszProblemArg,
LPSTR pszDetailsArg)
{
HRESULT hr = S_OK;
TraceCall("CRenameFolderInfo::SetError");
IxpAssert(FAILED(hrResult));
hrLastError = hrResult;
SafeMemFree(pszProblem);
SafeMemFree(pszDetails);
if (NULL != pszProblemArg)
{
pszProblem = PszDupA(pszProblemArg);
if (NULL == pszProblem)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
}
if (NULL != pszDetailsArg)
{
pszDetails = PszDupA(pszDetailsArg);
if (NULL == pszDetails)
{
hr = TraceResult(E_OUTOFMEMORY);
goto exit;
}
}
exit:
return hr;
}