//*************************************************************************** // 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 = ∣ 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 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 (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 * 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 * 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 = ∣ 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 :* // 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 /* 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 *), 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: 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; }