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