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

10249 lines
347 KiB

  1. //***************************************************************************
  2. // IMAP4 Message Sync Class Implementation (CIMAPSync)
  3. // Written by Raymond Cheng, 5/5/98
  4. // Copyright (C) Microsoft Corporation, 1998
  5. //***************************************************************************
  6. //---------------------------------------------------------------------------
  7. // Includes
  8. //---------------------------------------------------------------------------
  9. #include "pch.hxx"
  10. #include "imapsync.h"
  11. #include "xputil.h"
  12. #include "flagconv.h"
  13. #include "imapute.h"
  14. #include "storutil.h"
  15. #include "xpcomm.h"
  16. #include "iert.h"
  17. #include "menures.h"
  18. #include "serverq.h"
  19. #include "instance.h"
  20. #include "demand.h"
  21. #include "storecb.h"
  22. #define USE_QUEUING_LAYER
  23. //---------------------------------------------------------------------------
  24. // Module Data Types
  25. //---------------------------------------------------------------------------
  26. typedef struct tagFOLDERIDLIST
  27. {
  28. FOLDERID idFolder;
  29. struct tagFOLDERIDLIST *pfilNextFolderID;
  30. } FOLDERIDLISTNODE;
  31. //---------------------------------------------------------------------------
  32. // Module Constants
  33. //---------------------------------------------------------------------------
  34. #define CCHMAX_CMDLINE 512
  35. #define CCHMAX_IMAPFOLDERPATH 512
  36. #define CHASH_BUCKETS 50
  37. static const char cszIMAPFetchNewHdrsI4[] = "%lu:* (RFC822.HEADER RFC822.SIZE UID FLAGS INTERNALDATE)";
  38. static const char cszIMAPFetchNewHdrsI4r1[] =
  39. "%lu:* (BODY.PEEK[HEADER.FIELDS (References X-Ref X-Priority X-MSMail-Priority X-MSOESRec Newsgroups)] "
  40. "ENVELOPE RFC822.SIZE UID FLAGS INTERNALDATE)";
  41. static const char cszIMAPFetchCachedFlags[] = "1:%lu (UID FLAGS)";
  42. enum
  43. {
  44. tidDONT_CARE = 0, // Means that transaction ID is unimportant or unavailable
  45. tidSELECTION,
  46. tidFETCH_NEW_HDRS,
  47. tidFETCH_CACHED_FLAGS,
  48. tidCOPYMSGS, // The COPY command used to copy msgs to another IMAP fldr
  49. tidMOVEMSGS, // The STORE command used to delete msg ranges - currently only used for moves
  50. tidBODYMSN, // The FETCH command used to get MsgSeqNumToUID translation before tidBODY
  51. tidBODY, // The FETCH command used to retrieve a msg body
  52. tidNOOP, // The NOOP command used to poll for new messages
  53. tidCLOSE,
  54. tidSELECT,
  55. tidUPLOADMSG, // The APPEND command used to upload a message to IMAP server
  56. tidMARKMSGS,
  57. tidCREATE, // the CREATE cmd sent to create a folder
  58. tidCREATELIST, // the LIST command sent after a CREATE
  59. tidCREATESUBSCRIBE, // the SUBSCRIBE command sent after a CREATE
  60. tidHIERARCHYCHAR_LIST_B, // the LIST cmd sent to find hierarchy char (Plan B)
  61. tidHIERARCHYCHAR_CREATE, // the CREATE cmd sent to find hierarchy char
  62. tidHIERARCHYCHAR_LIST_C, // the LIST cmd sent to find hierarchy char (Plan C)
  63. tidHIERARCHYCHAR_DELETE, // the DELETE cmd sent to find hierarchy char
  64. tidPREFIXLIST, // Prefixed hierarchy listing (eg, "~raych/Mail" prefix)
  65. tidPREFIX_HC, // the LIST cmd sent to find hierarchy char for prefix
  66. tidPREFIX_CREATE, // the CREATE cmd sent to create the prefix folder
  67. tidDELETEFLDR, // the DELETE cmd sent to delete a folder
  68. tidDELETEFLDR_UNSUBSCRIBE, // the UNSUBSCRIBE cmd sent to unsub a deleted fldr
  69. tidRENAME, // The RENAME cmd sent to rename a folder
  70. tidRENAMESUBSCRIBE, // The subscribe cmd sent to subscribe a folder
  71. tidRENAMELIST, // The LIST cmd sent to check if rename was atomic
  72. tidRENAMERENAME, // The second rename attempt, if server does non-atomic rename
  73. tidRENAMESUBSCRIBE_AGAIN, // The subscribe cmd sent to attempt second new-tree subscribe again
  74. tidRENAMEUNSUBSCRIBE, // The unsubscribe cmd sent to unsubscribe the old folders
  75. tidSUBSCRIBE, // The (un)subscribe command sent to (un)subscribe a folder
  76. tidSPECIALFLDRLIST, // The LIST command sent to check if a special folder exists
  77. tidSPECIALFLDRLSUB, // The LSUB command sent to list a special folder's subscribed subfolders
  78. tidSPECIALFLDRSUBSCRIBE, // The SUBSCRIBE command sent out to subscribe an existing special folder
  79. tidFOLDERLIST,
  80. tidFOLDERLSUB,
  81. tidEXPUNGE, // EXPUNGE command
  82. tidSTATUS, // STATUS command used for IMessageServer::GetFolderCounts
  83. };
  84. enum
  85. {
  86. fbpNONE, // Fetch Body Part identifier (lpFetchCookie1 is set to this)
  87. fbpHEADER,
  88. fbpBODY,
  89. fbpUNKNOWN
  90. };
  91. // Priorities, for use with _EnqueueOperation
  92. enum
  93. {
  94. uiTOP_PRIORITY, // Ensures that we construct MsgSeqNum table before ALL user operations
  95. uiNORMAL_PRIORITY // Priority level for all user operations
  96. };
  97. // Argument Readability Defines
  98. const BOOL DONT_USE_UIDS = FALSE; // For use with IIMAPTransport
  99. const BOOL USE_UIDS = TRUE ; // For use with IIMAPTransport
  100. const BOOL fUPDATE_OLD_MSGFLAGS = TRUE; // For use with DownloadNewHeaders
  101. const BOOL fDONT_UPDATE_OLD_MSGFLAGS = FALSE; // For use with DownloadNewHeaders
  102. const BOOL fCOMPLETED = 1; // For use with NotifyMsgRecipients
  103. const BOOL fPROGRESS = 0; // For use with NotifyMsgRecipients
  104. const BOOL fLOAD_HC = FALSE; // (LoadSaveRootHierarchyChar): Load hierarchy character from foldercache
  105. const BOOL fSAVE_HC = TRUE; // (LoadSaveRootHierarchyChar): Save hierarchy character to foldercache
  106. const BOOL fHCF_PLAN_A_ONLY = TRUE; // Only execute Plan A in hierarchy char determination
  107. const BOOL fHCF_ALL_PLANS = FALSE; // Execute Plans A, B, C and Z in hierarchy char determination
  108. const BOOL fSUBSCRIBE = TRUE; // For use with SubscribeToFolder
  109. const BOOL fUNSUBSCRIBE = FALSE; // For use with SubscribeToFolder
  110. const BOOL fRECURSIVE = TRUE; // For use with DeleteFolderFromCache
  111. const BOOL fNON_RECURSIVE = FALSE; // For use with DeleteFolderFromCache
  112. const BOOL fINCLUDE_RENAME_FOLDER = TRUE; // For use with RenameTreeTraversal
  113. const BOOL fEXCLUDE_RENAME_FOLDER = FALSE; // For use with RenameTreeTraversal
  114. const BOOL fREMOVE = TRUE; // For use with IHashTable::Find
  115. const BOOL fNOPROGRESS = FALSE; // For use with CStoreCB::Initialize
  116. #define pahfoDONT_CREATE_FOLDER NULL // For use with FindHierarchalFolderName
  117. const HRESULT S_CREATED = 1; // Indicates FindHierarchicalFolderName created the fldr
  118. // None of the following bits can be set for a message to qualify as "unread"
  119. const DWORD dwIMAP_UNREAD_CRITERIA = IMAP_MSG_SEEN | IMAP_MSG_DELETED;
  120. // Internal flags for use with m_dwSyncToDo
  121. const DWORD SYNC_FOLDER_NOOP = 0x80000000;
  122. const DWORD AFTC_SUBSCRIBED = 0x00000001; // For use with AddToFolderCache's dwATFCFlags
  123. const DWORD AFTC_KEEPCHILDRENKNOWN = 0x00000002; // For use with AddToFolderCache's dwATFCFlags
  124. const DWORD AFTC_NOTSUBSCRIBED = 0x00000004; // For use with AddToFolderCache's dwATFCFlags
  125. const DWORD AFTC_NOTRANSLATION = 0x00000008; // For use with AddToFolderCache's dwATFCFlags
  126. #define AssertSingleThreaded AssertSz(m_dwThreadId == GetCurrentThreadId(), "The IMAPSync is not multithreaded. Someone is calling me on multiple threads")
  127. const DWORD snoDO_NOT_DISPOSE = 0x00000001; // For use with _SendNextOperation
  128. // Connection FSM
  129. const UINT WM_CFSM_EVENT = WM_USER;
  130. //---------------------------------------------------------------------------
  131. // Functions
  132. //---------------------------------------------------------------------------
  133. //***************************************************************************
  134. //***************************************************************************
  135. HRESULT CreateImapStore(IUnknown *pUnkOuter, IUnknown **ppUnknown)
  136. {
  137. CIMAPSync *pIMAPSync;
  138. HRESULT hr;
  139. TraceCall("CIMAPSync::CreateImapStore");
  140. IxpAssert(NULL != ppUnknown);
  141. // Initialize return values
  142. *ppUnknown = NULL;
  143. hr = E_NOINTERFACE;
  144. if (NULL != pUnkOuter)
  145. {
  146. hr = TraceResult(CLASS_E_NOAGGREGATION);
  147. goto exit;
  148. }
  149. pIMAPSync = new CIMAPSync;
  150. if (NULL == pIMAPSync)
  151. {
  152. hr = TraceResult(E_OUTOFMEMORY);
  153. goto exit;
  154. }
  155. #ifdef USE_QUEUING_LAYER
  156. hr = CreateServerQueue(pIMAPSync, (IMessageServer **)ppUnknown);
  157. pIMAPSync->Release(); // Since we're not returning this ptr, bump down refcount
  158. #else
  159. // If we reached this point, everything is working great
  160. *ppUnknown = SAFECAST(pIMAPSync, IMessageServer *);
  161. hr = S_OK;
  162. #endif
  163. exit:
  164. // Done
  165. return hr;
  166. }
  167. //***************************************************************************
  168. // Function: CIMAPSync (constructor)
  169. //***************************************************************************
  170. CIMAPSync::CIMAPSync(void)
  171. {
  172. TraceCall("CIMAPSync::CIMAPSync");
  173. m_cRef = 1;
  174. m_pTransport = NULL;
  175. ZeroMemory(&m_rInetServerInfo, sizeof(m_rInetServerInfo));
  176. m_idFolder = FOLDERID_INVALID;
  177. m_idSelectedFolder = FOLDERID_INVALID;
  178. m_idIMAPServer = FOLDERID_INVALID;
  179. m_pszAccountID = NULL;
  180. m_szAccountName[0] = '\0';
  181. m_pszFldrLeafName = NULL;
  182. m_pStore = NULL;
  183. m_pFolder = NULL;
  184. m_pDefCallback = NULL;
  185. m_pioNextOperation = NULL;
  186. m_dwMsgCount = 0;
  187. m_fMsgCountValid = FALSE;
  188. m_dwNumNewMsgs = 0;
  189. m_dwNumHdrsDLed = 0;
  190. m_dwNumUnreadDLed = 0;
  191. m_dwNumHdrsToDL = 0;
  192. m_dwUIDValidity = 0;
  193. m_dwSyncFolderFlags = 0;
  194. m_dwSyncToDo = 0;
  195. m_lSyncFolderRefCount = 0;
  196. m_dwHighestCachedUID = 0;
  197. m_rwsReadWriteStatus = rwsUNINITIALIZED;
  198. m_fCreateSpecial = TRUE;
  199. m_fNewMail = FALSE;
  200. m_fInbox = FALSE;
  201. m_fDidFullSync = FALSE;
  202. m_csNewConnState = CONNECT_STATE_DISCONNECT;
  203. m_cRootHierarchyChar = INVALID_HIERARCHY_CHAR;
  204. m_phcfHierarchyCharInfo = NULL;
  205. m_fReconnect = FALSE;
  206. m_issCurrent = issNotConnected;
  207. m_szRootFolderPrefix[0] = '\0';
  208. m_fPrefixExists = FALSE;
  209. // Central repository
  210. m_pCurrentCB = NULL;
  211. m_sotCurrent = SOT_INVALID;
  212. m_idCurrent = FOLDERID_INVALID;
  213. m_fSubscribe = FALSE;
  214. m_pCurrentHash = NULL;
  215. m_pListHash = NULL;
  216. m_fTerminating = FALSE;
  217. m_fInited = 0;
  218. m_fDisconnecting = 0;
  219. m_cFolders = 0;
  220. m_faStream = 0;
  221. m_pstmBody = NULL;
  222. m_idMessage = 0;
  223. m_fGotBody = FALSE;
  224. m_cfsState = CFSM_STATE_IDLE;
  225. m_cfsPrevState = CFSM_STATE_IDLE;
  226. m_hwndConnFSM = NULL;
  227. m_hrOperationResult = OLE_E_BLANK; // Uninitialized state
  228. m_szOperationProblem[0] = '\0';
  229. m_szOperationDetails[0] = '\0';
  230. m_dwThreadId = GetCurrentThreadId();
  231. }
  232. //***************************************************************************
  233. // Function: ~CIMAPSync (destructor)
  234. //***************************************************************************
  235. CIMAPSync::~CIMAPSync(void)
  236. {
  237. TraceCall("CIMAPSync::~CIMAPSync");
  238. IxpAssert(0 == m_cRef);
  239. if (NULL != m_phcfHierarchyCharInfo)
  240. delete m_phcfHierarchyCharInfo;
  241. ZeroMemory(&m_rInetServerInfo, sizeof(m_rInetServerInfo)); // Done for security
  242. IxpAssert (!IsWindow(m_hwndConnFSM));
  243. SafeMemFree(m_pszAccountID);
  244. SafeMemFree(m_pszFldrLeafName);
  245. SafeRelease(m_pStore);
  246. SafeRelease(m_pFolder);
  247. }
  248. HRESULT CIMAPSync::QueryInterface(REFIID iid, void **ppvObject)
  249. {
  250. HRESULT hr;
  251. TraceCall("CIMAPSync::QueryInterface");
  252. AssertSingleThreaded;
  253. IxpAssert(m_cRef > 0);
  254. IxpAssert(NULL != ppvObject);
  255. // Init variables, arguments
  256. hr = E_NOINTERFACE;
  257. if (NULL == ppvObject)
  258. goto exit;
  259. *ppvObject = NULL;
  260. // Find a ptr to the interface
  261. if (IID_IUnknown == iid)
  262. *ppvObject = (IMessageServer *) this;
  263. else if (IID_IMessageServer == iid)
  264. *ppvObject = (IMessageServer *) this;
  265. else if (IID_ITransportCallback == iid)
  266. *ppvObject = (ITransportCallback *) this;
  267. else if (IID_ITransportCallbackService == iid)
  268. *ppvObject = (ITransportCallbackService *) this;
  269. else if (IID_IIMAPCallback == iid)
  270. *ppvObject = (IIMAPCallback *) this;
  271. else if (IID_IIMAPStore == iid)
  272. *ppvObject = (IIMAPStore *) this;
  273. // If we returned an interface, return success
  274. if (NULL != *ppvObject)
  275. {
  276. hr = S_OK;
  277. ((IUnknown *) *ppvObject)->AddRef();
  278. }
  279. exit:
  280. return hr;
  281. }
  282. ULONG CIMAPSync::AddRef(void)
  283. {
  284. TraceCall("CIMAPSync::AddRef");
  285. AssertSingleThreaded;
  286. IxpAssert(m_cRef > 0);
  287. m_cRef += 1;
  288. DOUT ("CIMAPSync::AddRef, returned Ref Count=%ld", m_cRef);
  289. return m_cRef;
  290. }
  291. ULONG CIMAPSync::Release(void)
  292. {
  293. TraceCall("CIMAPSync::Release");
  294. AssertSingleThreaded;
  295. IxpAssert(m_cRef > 0);
  296. m_cRef -= 1;
  297. DOUT("CIMAPSync::Release, returned Ref Count = %ld", m_cRef);
  298. if (0 == m_cRef)
  299. {
  300. delete this;
  301. return 0;
  302. }
  303. else
  304. return m_cRef;
  305. }
  306. //===========================================================================
  307. // IMessageSync Methods
  308. //===========================================================================
  309. //***************************************************************************
  310. //***************************************************************************
  311. HRESULT CIMAPSync::Initialize(IMessageStore *pStore, FOLDERID idStoreRoot, IMessageFolder *pFolder, FOLDERID idFolder)
  312. {
  313. HRESULT hr;
  314. BOOL fResult;
  315. WNDCLASSEX wc;
  316. TraceCall("CIMAPSync::Initialize");
  317. AssertSingleThreaded;
  318. if (pStore == NULL || idStoreRoot == FOLDERID_INVALID)
  319. {
  320. hr = TraceResult(E_INVALIDARG);
  321. goto exit;
  322. }
  323. // check to make sure we're not inited twice.
  324. if (m_fInited)
  325. {
  326. hr = TraceResult(CO_E_ALREADYINITIALIZED);
  327. goto exit;
  328. }
  329. // Save current folder data
  330. m_idIMAPServer = idStoreRoot;
  331. m_idFolder = idFolder;
  332. ReplaceInterface(m_pStore, pStore);
  333. ReplaceInterface(m_pFolder, pFolder);
  334. LoadLeafFldrName(idFolder);
  335. hr = _LoadAccountInfo();
  336. if (FAILED(hr))
  337. {
  338. TraceResult(hr);
  339. goto exit;
  340. }
  341. hr = _LoadTransport();
  342. if (FAILED(hr))
  343. {
  344. TraceResult(hr);
  345. goto exit;
  346. }
  347. // Create a window to queue Connection FSM messages
  348. wc.cbSize = sizeof(WNDCLASSEX);
  349. fResult = GetClassInfoEx(g_hInst, c_szIMAPSyncCFSMWndClass, &wc);
  350. if (FALSE == fResult)
  351. {
  352. ATOM aResult;
  353. // Register this window class
  354. wc.style = 0;
  355. wc.lpfnWndProc = CIMAPSync::_ConnFSMWndProc;
  356. wc.cbClsExtra = 0;
  357. wc.cbWndExtra = 0;
  358. wc.hInstance = g_hInst;
  359. wc.hIcon = NULL;
  360. wc.hCursor = NULL;
  361. wc.hbrBackground = NULL;
  362. wc.lpszMenuName = NULL;
  363. wc.lpszClassName = c_szIMAPSyncCFSMWndClass;
  364. wc.hIconSm = NULL;
  365. aResult = RegisterClassEx(&wc);
  366. if (0 == aResult && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
  367. {
  368. hr = TraceResult(E_FAIL);
  369. goto exit;
  370. }
  371. }
  372. m_hwndConnFSM = CreateWindowEx(WS_EX_TOPMOST, c_szIMAPSyncCFSMWndClass,
  373. c_szIMAPSyncCFSMWndClass, WS_POPUP, 1, 1, 1, 1, NULL, NULL, g_hInst,
  374. (LPVOID)this);
  375. if (NULL == m_hwndConnFSM)
  376. {
  377. hr = TraceResult(E_FAIL);
  378. goto exit;
  379. }
  380. // flag successful initialization
  381. m_fInited = TRUE;
  382. exit:
  383. return hr;
  384. }
  385. HRESULT CIMAPSync::ResetFolder(IMessageFolder *pFolder, FOLDERID idFolder)
  386. {
  387. TraceCall("CIMAPSync::ResetFolder");
  388. Assert(m_cRef > 0);
  389. m_idFolder = idFolder;
  390. ReplaceInterface(m_pFolder, pFolder);
  391. LoadLeafFldrName(idFolder);
  392. return S_OK;
  393. }
  394. void CIMAPSync::LoadLeafFldrName(FOLDERID idFolder)
  395. {
  396. FOLDERINFO fiFolderInfo;
  397. SafeMemFree(m_pszFldrLeafName);
  398. if (FOLDERID_INVALID != idFolder)
  399. {
  400. HRESULT hr;
  401. hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
  402. if (SUCCEEDED(hr))
  403. {
  404. m_pszFldrLeafName = PszDupA(fiFolderInfo.pszName);
  405. if (NULL == m_pszFldrLeafName)
  406. {
  407. TraceResult(E_OUTOFMEMORY);
  408. m_pszFldrLeafName = PszDupA(""); // If this fails, tough luck
  409. }
  410. m_pStore->FreeRecord(&fiFolderInfo);
  411. }
  412. }
  413. }
  414. HRESULT CIMAPSync::Close(DWORD dwFlags)
  415. {
  416. HRESULT hrTemp;
  417. BOOL fCancelOperation = FALSE;
  418. STOREERROR seErrorInfo;
  419. IStoreCallback *pCallback = NULL;
  420. STOREOPERATIONTYPE sotCurrent;
  421. TraceCall("CIMAPSync::Close");
  422. AssertSingleThreaded;
  423. // validate flags
  424. if (0 == (dwFlags & (MSGSVRF_HANDS_OFF_SERVER | MSGSVRF_DROP_CONNECTION)))
  425. return TraceResult(E_UNEXPECTED);
  426. // Check if we are to cancel the current operation
  427. if (SOT_INVALID != m_sotCurrent &&
  428. (dwFlags & (MSGSVRF_DROP_CONNECTION | MSGSVRF_HANDS_OFF_SERVER)))
  429. {
  430. fCancelOperation = TRUE;
  431. if (NULL != m_pCurrentCB)
  432. {
  433. IxpAssert(SOT_INVALID != m_sotCurrent);
  434. FillStoreError(&seErrorInfo, STORE_E_OPERATION_CANCELED, 0,
  435. MAKEINTRESOURCE(IDS_IXP_E_USER_CANCEL), NULL);
  436. // Remember how to call callback
  437. pCallback = m_pCurrentCB;
  438. sotCurrent = m_sotCurrent;
  439. }
  440. // Reset current operation vars
  441. m_hrOperationResult = OLE_E_BLANK;
  442. m_sotCurrent = SOT_INVALID;
  443. m_pCurrentCB = NULL;
  444. m_cfsState = CFSM_STATE_IDLE;
  445. m_cfsPrevState = CFSM_STATE_IDLE;
  446. m_fTerminating = FALSE;
  447. // Clear the Connection FSM event queue
  448. if (IsWindow(m_hwndConnFSM))
  449. {
  450. MSG msg;
  451. while (PeekMessage(&msg, m_hwndConnFSM, WM_CFSM_EVENT, WM_CFSM_EVENT, PM_REMOVE))
  452. {
  453. TraceInfoTag(TAG_IMAPSYNC,
  454. _MSG("CIMAPSync::Close removing WM_CFSM_EVENT, cfeEvent = %lX",
  455. msg.wParam));
  456. }
  457. }
  458. }
  459. // If connection still exists, perform purge-on-exit and disconnect us as required
  460. // Connection might not exist, however (eg, if modem connection terminated)
  461. if (dwFlags & MSGSVRF_DROP_CONNECTION || dwFlags & MSGSVRF_HANDS_OFF_SERVER)
  462. {
  463. if (m_pTransport)
  464. {
  465. m_fDisconnecting = TRUE;
  466. m_pTransport->DropConnection();
  467. }
  468. }
  469. SafeRelease(m_pCurrentHash);
  470. SafeRelease(m_pListHash);
  471. SafeRelease(m_pstmBody);
  472. if (dwFlags & MSGSVRF_HANDS_OFF_SERVER)
  473. {
  474. SafeRelease(m_pDefCallback);
  475. FlushOperationQueue(issNotConnected, STORE_E_OPERATION_CANCELED);
  476. if (IsWindow(m_hwndConnFSM))
  477. {
  478. if (m_dwThreadId == GetCurrentThreadId())
  479. SideAssert(DestroyWindow(m_hwndConnFSM));
  480. else
  481. SideAssert(PostMessage(m_hwndConnFSM, WM_CLOSE, 0, 0));
  482. }
  483. // Let go of our transport object
  484. if (m_pTransport)
  485. {
  486. m_pTransport->HandsOffCallback();
  487. m_pTransport->Release();
  488. m_pTransport = NULL;
  489. }
  490. m_fInited = 0;
  491. }
  492. // Notify caller that we're complete
  493. if (fCancelOperation && NULL != pCallback)
  494. {
  495. HRESULT hrTemp;
  496. hrTemp = pCallback->OnComplete(sotCurrent, seErrorInfo.hrResult, NULL, &seErrorInfo);
  497. TraceError(hrTemp);
  498. pCallback->Release();
  499. }
  500. // *** WARNING: After this point, OnComplete may have been called which may cause
  501. // us to have been re-entered. Make no reference to module vars!
  502. return S_OK;
  503. }
  504. HRESULT CIMAPSync::PurgeMessageProgress(HWND hwndParent)
  505. {
  506. CStoreCB *pCB = NULL;
  507. HRESULT hrResult = S_OK;
  508. TraceCall("CIMAPSync::PurgeMessageProgress");
  509. // Check if we're connected and selected
  510. if (NULL == m_pTransport || issSelected != m_issCurrent ||
  511. FOLDERID_INVALID == m_idSelectedFolder || m_idSelectedFolder != m_idFolder ||
  512. CFSM_STATE_IDLE != m_cfsState)
  513. {
  514. // Not in proper state to issue CLOSE
  515. goto exit;
  516. }
  517. pCB = new CStoreCB;
  518. if (NULL == pCB)
  519. {
  520. hrResult = TraceResult(E_OUTOFMEMORY);
  521. goto exit;
  522. }
  523. hrResult = pCB->Initialize(hwndParent, MAKEINTRESOURCE(idsPurgingMessages), fNOPROGRESS);
  524. if (FAILED(hrResult))
  525. {
  526. TraceResult(hrResult);
  527. goto exit;
  528. }
  529. // Issue the CLOSE command
  530. hrResult = _EnqueueOperation(tidCLOSE, 0, icCLOSE_COMMAND, NULL, uiNORMAL_PRIORITY);
  531. if (FAILED(hrResult))
  532. {
  533. TraceResult(hrResult);
  534. goto exit;
  535. }
  536. hrResult = _BeginOperation(SOT_PURGING_MESSAGES, pCB);
  537. if (FAILED(hrResult) && E_PENDING != hrResult)
  538. {
  539. TraceResult(hrResult);
  540. goto exit;
  541. }
  542. // Wait until CLOSE is complete
  543. hrResult = pCB->Block();
  544. TraceError(hrResult);
  545. // Shut down
  546. hrResult = pCB->Close();
  547. TraceError(hrResult);
  548. exit:
  549. SafeRelease(pCB);
  550. return hrResult;
  551. }
  552. HRESULT CIMAPSync::_ConnFSM_HandleEvent(CONN_FSM_EVENT cfeEvent)
  553. {
  554. HRESULT hrResult = S_OK;
  555. IxpAssert(m_cRef > 0);
  556. TraceCall("CIMAPSync::_HandleConnFSMEvent");
  557. if (cfeEvent >= CFSM_EVENT_MAX)
  558. {
  559. hrResult = TraceResult(E_INVALIDARG);
  560. goto exit;
  561. }
  562. if (m_cfsState >= CFSM_STATE_MAX)
  563. {
  564. hrResult = TraceResult(E_FAIL);
  565. goto exit;
  566. }
  567. IxpAssert(NULL != c_pConnFSMEventHandlers[m_cfsState]);
  568. hrResult = (this->*c_pConnFSMEventHandlers[m_cfsState])(cfeEvent);
  569. if (FAILED(hrResult))
  570. {
  571. TraceResult(hrResult);
  572. goto exit;
  573. }
  574. exit:
  575. return hrResult;
  576. } // _ConnFSM_HandleEvent
  577. HRESULT CIMAPSync::_ConnFSM_Idle(CONN_FSM_EVENT cfeEvent)
  578. {
  579. HRESULT hrResult = S_OK;
  580. IxpAssert(m_cRef > 0);
  581. IxpAssert(CFSM_STATE_IDLE == m_cfsState);
  582. TraceCall("CIMAPSync::_ConnFSM_Idle");
  583. switch (cfeEvent)
  584. {
  585. case CFSM_EVENT_INITIALIZE:
  586. // Don't need to do anything for this state
  587. break;
  588. case CFSM_EVENT_CMDAVAIL:
  589. hrResult = _ConnFSM_ChangeState(CFSM_STATE_WAITFORCONN);
  590. if (FAILED(hrResult))
  591. {
  592. TraceResult(hrResult);
  593. goto exit;
  594. }
  595. break;
  596. case CFSM_EVENT_ERROR:
  597. // We don't care about no stinking errors (not in this state)
  598. break;
  599. default:
  600. TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_Idle, got cfeEvent = %lu", cfeEvent));
  601. hrResult = TraceResult(E_INVALIDARG);
  602. break;
  603. } // switch
  604. exit:
  605. return hrResult;
  606. } // _ConnFSM_Idle
  607. HRESULT CIMAPSync::_ConnFSM_WaitForConn(CONN_FSM_EVENT cfeEvent)
  608. {
  609. HRESULT hrResult = S_OK;
  610. BOOL fAbort = FALSE;
  611. IxpAssert(m_cRef > 0);
  612. IxpAssert(CFSM_STATE_WAITFORCONN == m_cfsState);
  613. TraceCall("CIMAPSync::_ConnFSM_WaitForConn");
  614. switch (cfeEvent)
  615. {
  616. case CFSM_EVENT_INITIALIZE:
  617. // We need to connect and authenticate. Do this even if we're already
  618. // connected (we will check if user changed connection settings)
  619. hrResult = SetConnectionState(CONNECT_STATE_CONNECT);
  620. if (FAILED(hrResult))
  621. {
  622. TraceResult(hrResult);
  623. goto exit;
  624. }
  625. break;
  626. case CFSM_EVENT_CONNCOMPLETE:
  627. hrResult = _ConnFSM_ChangeState(CFSM_STATE_WAITFORSELECT);
  628. if (FAILED(hrResult))
  629. {
  630. TraceResult(hrResult);
  631. goto exit;
  632. }
  633. break;
  634. case CFSM_EVENT_ERROR:
  635. fAbort = TRUE;
  636. break;
  637. default:
  638. TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_WaitForConn, got cfeEvent = %lu", cfeEvent));
  639. hrResult = TraceResult(E_INVALIDARG);
  640. break;
  641. } // switch
  642. exit:
  643. if (FAILED(hrResult) || fAbort)
  644. {
  645. HRESULT hrTemp;
  646. // Looks like we're going to have to dump this operation
  647. hrTemp = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
  648. TraceError(hrTemp);
  649. }
  650. return hrResult;
  651. } // _ConnFSM_WaitForConn
  652. HRESULT CIMAPSync::_ConnFSM_WaitForSelect(CONN_FSM_EVENT cfeEvent)
  653. {
  654. HRESULT hrResult = S_OK;
  655. BOOL fGoToNextState = FALSE;
  656. BOOL fAbort = FALSE;
  657. IxpAssert(m_cRef > 0);
  658. IxpAssert(CFSM_STATE_WAITFORSELECT == m_cfsState);
  659. TraceCall("CIMAPSync::_ConnFSM_WaitForSelect");
  660. switch (cfeEvent)
  661. {
  662. case CFSM_EVENT_INITIALIZE:
  663. // Do we need to SELECT the current folder for this operation?
  664. if (_StoreOpToMinISS(m_sotCurrent) < issSelected)
  665. {
  666. // This operation does not require folder selection: ready to start operation
  667. hrResult = _ConnFSM_ChangeState(CFSM_STATE_STARTOPERATION);
  668. if (FAILED(hrResult))
  669. {
  670. TraceResult(hrResult);
  671. goto exit;
  672. }
  673. }
  674. else
  675. {
  676. // Issue the SELECT command for the current folder
  677. hrResult = _EnsureSelected();
  678. if (FAILED(hrResult))
  679. {
  680. TraceResult(hrResult);
  681. goto exit;
  682. }
  683. else if (STORE_S_NOOP == hrResult)
  684. fGoToNextState= TRUE;
  685. }
  686. if (FALSE == fGoToNextState)
  687. break;
  688. // *** If fGoToNextState, FALL THROUGH ***
  689. case CFSM_EVENT_SELECTCOMPLETE:
  690. hrResult = _ConnFSM_ChangeState(CFSM_STATE_WAITFORHDRSYNC);
  691. if (FAILED(hrResult))
  692. {
  693. TraceResult(hrResult);
  694. goto exit;
  695. }
  696. break;
  697. case CFSM_EVENT_ERROR:
  698. fAbort = TRUE;
  699. break;
  700. default:
  701. TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_WaitForSelect, got cfeEvent = %lu", cfeEvent));
  702. hrResult = TraceResult(E_INVALIDARG);
  703. break;
  704. } // switch
  705. exit:
  706. if (FAILED(hrResult) || fAbort)
  707. {
  708. HRESULT hrTemp;
  709. // Looks like we're going to have to dump this operation
  710. hrTemp = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
  711. TraceError(hrTemp);
  712. }
  713. return hrResult;
  714. } // _ConnFSM_WaitForSelect
  715. HRESULT CIMAPSync::_ConnFSM_WaitForHdrSync(CONN_FSM_EVENT cfeEvent)
  716. {
  717. HRESULT hrResult=S_OK;
  718. BOOL fAbort = FALSE;
  719. IxpAssert(m_cRef > 0);
  720. IxpAssert(CFSM_STATE_WAITFORHDRSYNC == m_cfsState);
  721. TraceCall("CIMAPSync::_ConnFSM_WaitForHdrSync");
  722. switch (cfeEvent)
  723. {
  724. case CFSM_EVENT_INITIALIZE:
  725. // Check if we're supposed to synchronize this folder
  726. if (0 != m_dwSyncToDo)
  727. {
  728. // Yup, send the synchronization commands
  729. Assert(0 == m_lSyncFolderRefCount);
  730. m_lSyncFolderRefCount = 0;
  731. hrResult = _SyncHeader();
  732. if (FAILED(hrResult))
  733. {
  734. TraceResult(hrResult);
  735. goto exit;
  736. }
  737. }
  738. else
  739. // No synchronization requested
  740. hrResult = STORE_S_NOOP;
  741. // If no synchronization requested, fall through and proceed to next state
  742. if (STORE_S_NOOP != hrResult)
  743. break; // Our work here is done
  744. // *** FALL THROUGH ***
  745. case CFSM_EVENT_HDRSYNCCOMPLETE:
  746. hrResult = _ConnFSM_ChangeState(CFSM_STATE_STARTOPERATION);
  747. if (FAILED(hrResult))
  748. {
  749. TraceResult(hrResult);
  750. goto exit;
  751. }
  752. break;
  753. case CFSM_EVENT_ERROR:
  754. fAbort = TRUE;
  755. break;
  756. default:
  757. TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_WaitForHdrSync, got cfeEvent = %lu", cfeEvent));
  758. hrResult = TraceResult(E_INVALIDARG);
  759. break;
  760. } // switch
  761. exit:
  762. if (FAILED(hrResult) || fAbort)
  763. {
  764. HRESULT hrTemp;
  765. // Looks like we're going to have to dump this operation
  766. hrTemp = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
  767. TraceError(hrTemp);
  768. }
  769. return hrResult;
  770. } // _ConnFSM_WaitForHdrSync
  771. HRESULT CIMAPSync::_ConnFSM_StartOperation(CONN_FSM_EVENT cfeEvent)
  772. {
  773. HRESULT hrResult = S_OK;
  774. BOOL fMoreCmdsToSend;
  775. BOOL fAbort = FALSE;
  776. IxpAssert(m_cRef > 0);
  777. IxpAssert(CFSM_STATE_STARTOPERATION == m_cfsState);
  778. TraceCall("CIMAPSync::_ConnFSM_StartOperation");
  779. switch (cfeEvent)
  780. {
  781. case CFSM_EVENT_INITIALIZE:
  782. // Launch operation
  783. hrResult = _LaunchOperation();
  784. if (FAILED(hrResult))
  785. {
  786. TraceResult(hrResult);
  787. goto exit;
  788. }
  789. else if (STORE_S_NOOP == hrResult)
  790. {
  791. // This means success, but no operation launched. Proceed directly to "DONE"
  792. hrResult = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
  793. if (FAILED(hrResult))
  794. {
  795. TraceResult(hrResult);
  796. goto exit;
  797. }
  798. }
  799. else
  800. {
  801. // Proceed to the next state to wait for command completion
  802. hrResult = _ConnFSM_ChangeState(CFSM_STATE_WAITFOROPERATIONDONE);
  803. if (FAILED(hrResult))
  804. {
  805. TraceResult(hrResult);
  806. goto exit;
  807. }
  808. }
  809. break;
  810. case CFSM_EVENT_ERROR:
  811. fAbort = TRUE;
  812. break;
  813. default:
  814. TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_StartOperation, got cfeEvent = %lu", cfeEvent));
  815. hrResult = TraceResult(E_INVALIDARG);
  816. break;
  817. } // switch
  818. exit:
  819. if (FAILED(hrResult) || fAbort)
  820. {
  821. HRESULT hrTemp;
  822. // Looks like we're going to have to dump this operation
  823. hrTemp = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
  824. TraceError(hrTemp);
  825. }
  826. return hrResult;
  827. } // _ConnFSM_StartOperation
  828. HRESULT CIMAPSync::_ConnFSM_WaitForOpDone(CONN_FSM_EVENT cfeEvent)
  829. {
  830. HRESULT hrResult = S_OK;
  831. IxpAssert(m_cRef > 0);
  832. IxpAssert(CFSM_STATE_WAITFOROPERATIONDONE == m_cfsState);
  833. TraceCall("CIMAPSync::_ConnFSM_WaitForOpDone");
  834. switch (cfeEvent)
  835. {
  836. case CFSM_EVENT_INITIALIZE:
  837. // No need to do anything for initialization
  838. break;
  839. case CFSM_EVENT_OPERATIONCOMPLETE:
  840. case CFSM_EVENT_ERROR:
  841. // Proceed to next state
  842. hrResult = _ConnFSM_ChangeState(CFSM_STATE_OPERATIONCOMPLETE);
  843. if (FAILED(hrResult))
  844. {
  845. TraceResult(hrResult);
  846. goto exit;
  847. }
  848. break;
  849. default:
  850. TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_WaitForOpDone, got cfeEvent = %lu", cfeEvent));
  851. hrResult = TraceResult(E_INVALIDARG);
  852. break;
  853. } // switch
  854. exit:
  855. return hrResult;
  856. } // _ConnFSM_WaitForOpDone
  857. HRESULT CIMAPSync::_ConnFSM_OperationComplete(CONN_FSM_EVENT cfeEvent)
  858. {
  859. HRESULT hrResult = S_OK;
  860. IxpAssert(m_cRef > 0);
  861. IxpAssert(CFSM_STATE_OPERATIONCOMPLETE == m_cfsState);
  862. TraceCall("CIMAPSync::_ConnFSM_OperationComplete");
  863. switch (cfeEvent)
  864. {
  865. case CFSM_EVENT_INITIALIZE:
  866. // Clean up and send OnComplete callback to caller
  867. hrResult = _OnOperationComplete();
  868. // Proceed back to the IDLE state
  869. hrResult = _ConnFSM_ChangeState(CFSM_STATE_IDLE);
  870. if (FAILED(hrResult))
  871. {
  872. TraceResult(hrResult);
  873. goto exit;
  874. }
  875. break;
  876. case CFSM_EVENT_ERROR:
  877. // Ignore errors, we're on the way back to IDLE
  878. break;
  879. default:
  880. TraceInfoTag(TAG_IMAPSYNC, _MSG("CIMAPSync::_ConnFSM_OperationComplete, got cfeEvent = %lu", cfeEvent));
  881. hrResult = TraceResult(E_INVALIDARG);
  882. break;
  883. } // switch
  884. exit:
  885. return hrResult;
  886. } // _ConnFSM_OperationComplete
  887. HRESULT CIMAPSync::_ConnFSM_ChangeState(CONN_FSM_STATE cfsNewState)
  888. {
  889. HRESULT hrResult;
  890. IxpAssert(m_cRef > 0);
  891. IxpAssert(cfsNewState < CFSM_STATE_MAX);
  892. TraceCall("CIMAPSync::_ConnFSM_ChangeState");
  893. if (CFSM_STATE_OPERATIONCOMPLETE == cfsNewState)
  894. m_fTerminating = TRUE;
  895. m_cfsPrevState = m_cfsState;
  896. m_cfsState = cfsNewState;
  897. hrResult = _ConnFSM_QueueEvent(CFSM_EVENT_INITIALIZE);
  898. if (FAILED(hrResult))
  899. {
  900. TraceResult(hrResult);
  901. goto exit;
  902. }
  903. exit:
  904. return hrResult;
  905. } // _ConnFSM_ChangeState
  906. HRESULT CIMAPSync::_ConnFSM_QueueEvent(CONN_FSM_EVENT cfeEvent)
  907. {
  908. BOOL fResult;
  909. HRESULT hrResult = S_OK;
  910. IxpAssert(m_cRef > 0);
  911. IxpAssert(cfeEvent < CFSM_EVENT_MAX);
  912. TraceCall("CIMAPSync::_ConnFSM_QueueEvent");
  913. fResult = PostMessage(m_hwndConnFSM, WM_CFSM_EVENT, cfeEvent, 0);
  914. if (0 == fResult)
  915. {
  916. hrResult = TraceResult(E_FAIL);
  917. goto exit;
  918. }
  919. exit:
  920. return hrResult;
  921. } // _ConnFSM_QueueEvent
  922. HRESULT CIMAPSync::_LaunchOperation(void)
  923. {
  924. HRESULT hrResult = E_FAIL;
  925. IxpAssert(m_cRef > 0);
  926. IxpAssert(CFSM_STATE_STARTOPERATION == m_cfsState);
  927. TraceCall("CIMAPSync::_LaunchOperation");
  928. switch (m_sotCurrent)
  929. {
  930. case SOT_SYNC_FOLDER:
  931. IxpAssert(OLE_E_BLANK == m_hrOperationResult);
  932. hrResult = STORE_S_NOOP; // Nothing to do! We're already done!
  933. m_hrOperationResult = S_OK; // If we got this far we must be successful
  934. goto exit;
  935. default:
  936. // Do nothing for now
  937. break;
  938. } // switch
  939. // Launch Operation (for now, this just means to pump send queue)
  940. do
  941. {
  942. hrResult = _SendNextOperation(NOFLAGS);
  943. TraceError(hrResult);
  944. } while (S_OK == hrResult);
  945. exit:
  946. return hrResult;
  947. } // _LaunchOperation
  948. HRESULT CIMAPSync::_OnOperationComplete(void)
  949. {
  950. STOREERROR seErrorInfo;
  951. STOREERROR *pErrorInfo = NULL;
  952. HRESULT hrTemp;
  953. HRESULT hrOperationResult;
  954. IStoreCallback *pCallback;
  955. STOREOPERATIONTYPE sotCurrent;
  956. IxpAssert(m_cRef > 0);
  957. IxpAssert(CFSM_STATE_OPERATIONCOMPLETE == m_cfsState);
  958. // In some cases, CIMAPSync::Close does the OnComplete call for us
  959. if (SOT_INVALID == m_sotCurrent)
  960. {
  961. IxpAssert(NULL == m_pCurrentCB);
  962. IxpAssert(OLE_E_BLANK == m_hrOperationResult);
  963. return S_OK;
  964. }
  965. IxpAssert(OLE_E_BLANK != m_hrOperationResult);
  966. TraceCall("CIMAPSync::_OnOperationComplete");
  967. if (NULL != m_pCurrentCB && FAILED(m_hrOperationResult))
  968. {
  969. FillStoreError(&seErrorInfo, m_hrOperationResult, 0, NULL, NULL);
  970. pErrorInfo = &seErrorInfo;
  971. }
  972. // Ancient relic of the past: will be deleted when queue is removed
  973. FlushOperationQueue(issNotConnected, E_FAIL);
  974. // Remember a couple of things
  975. pCallback = m_pCurrentCB;
  976. sotCurrent = m_sotCurrent;
  977. hrOperationResult = m_hrOperationResult;
  978. // Reset all operation variables in case of re-entry during OnComplete call
  979. m_pCurrentCB = NULL;
  980. m_sotCurrent = SOT_INVALID;
  981. m_hrOperationResult = OLE_E_BLANK;
  982. m_fTerminating = FALSE;
  983. m_idCurrent = FOLDERID_INVALID;
  984. m_fSubscribe = FALSE;
  985. SafeRelease(m_pCurrentHash);
  986. SafeRelease(m_pListHash);
  987. // Now we're ready to call OnComplete
  988. if (NULL != pCallback)
  989. {
  990. // This should be the ONLY call to IStoreCallback::OnComplete in this class!
  991. hrTemp = pCallback->OnComplete(sotCurrent, hrOperationResult, NULL, pErrorInfo);
  992. TraceError(hrTemp);
  993. // *** WARNING: At this point, we may be re-entered if OnComplete call puts up
  994. // window. Make no references to module vars!
  995. pCallback->Release();
  996. }
  997. return S_OK;
  998. } // _OnOperationComplete
  999. IMAP_SERVERSTATE CIMAPSync::_StoreOpToMinISS(STOREOPERATIONTYPE sot)
  1000. {
  1001. IMAP_SERVERSTATE issResult = issSelected;
  1002. switch (sot)
  1003. {
  1004. case SOT_INVALID:
  1005. IxpAssert(FALSE);
  1006. break;
  1007. case SOT_CONNECTION_STATUS:
  1008. case SOT_PUT_MESSAGE:
  1009. case SOT_SYNCING_STORE:
  1010. case SOT_CREATE_FOLDER:
  1011. case SOT_MOVE_FOLDER:
  1012. case SOT_DELETE_FOLDER:
  1013. case SOT_RENAME_FOLDER:
  1014. case SOT_SUBSCRIBE_FOLDER:
  1015. case SOT_UPDATE_FOLDER:
  1016. case SOT_SYNCING_DESCRIPTIONS:
  1017. issResult = issAuthenticated;
  1018. break;
  1019. case SOT_SYNC_FOLDER:
  1020. case SOT_GET_MESSAGE:
  1021. case SOT_COPYMOVE_MESSAGE:
  1022. case SOT_SEARCHING:
  1023. case SOT_DELETING_MESSAGES:
  1024. case SOT_SET_MESSAGEFLAGS:
  1025. case SOT_PURGING_MESSAGES:
  1026. issResult = issSelected;
  1027. break;
  1028. default:
  1029. IxpAssert(FALSE);
  1030. break;
  1031. } // switch
  1032. return issResult;
  1033. } // _StoreOpToMinISS
  1034. LRESULT CALLBACK CIMAPSync::_ConnFSMWndProc(HWND hwnd, UINT uMsg,
  1035. WPARAM wParam, LPARAM lParam)
  1036. {
  1037. LRESULT lResult = 0;
  1038. CIMAPSync *pThis;
  1039. HRESULT hrTemp;
  1040. pThis = (CIMAPSync *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
  1041. switch (uMsg)
  1042. {
  1043. case WM_CREATE:
  1044. IxpAssert(NULL == pThis);
  1045. pThis = (CIMAPSync *)((CREATESTRUCT *)lParam)->lpCreateParams;
  1046. SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM)pThis);
  1047. lResult = 0;
  1048. break;
  1049. case WM_DESTROY:
  1050. SetWindowLongPtr(hwnd, GWLP_USERDATA, NULL);
  1051. break;
  1052. case WM_CFSM_EVENT:
  1053. IxpAssert(wParam < CFSM_EVENT_MAX);
  1054. IxpAssert(0 == lParam);
  1055. IxpAssert(IsWindow(hwnd));
  1056. hrTemp = pThis->_ConnFSM_HandleEvent((CONN_FSM_EVENT)wParam);
  1057. if (FAILED(hrTemp))
  1058. {
  1059. TraceResult(hrTemp);
  1060. pThis->m_hrOperationResult = hrTemp;
  1061. }
  1062. break;
  1063. default:
  1064. lResult = DefWindowProc(hwnd, uMsg, wParam, lParam);
  1065. break;
  1066. }
  1067. return lResult;
  1068. }
  1069. HRESULT CIMAPSync::_EnsureInited()
  1070. {
  1071. if (!m_fInited)
  1072. return CO_E_NOTINITIALIZED;
  1073. if (!m_pTransport)
  1074. return E_UNEXPECTED;
  1075. if (m_sotCurrent != SOT_INVALID)
  1076. {
  1077. AssertSz(m_sotCurrent != SOT_INVALID, "IMAPSync was called into during a command-execution. Bug in server queue?");
  1078. return E_UNEXPECTED;
  1079. }
  1080. return S_OK;
  1081. }
  1082. /*
  1083. * Function : EnsureSelected()
  1084. *
  1085. * Purpose: make sure we are in the selected folder state
  1086. * if we are selected, then we're done.
  1087. *
  1088. *
  1089. */
  1090. HRESULT CIMAPSync::_EnsureSelected(void)
  1091. {
  1092. HRESULT hr;
  1093. LPSTR pszDestFldrPath = NULL;
  1094. TraceCall("CIMAPSync::_EnsureSelected");
  1095. AssertSingleThreaded;
  1096. IxpAssert(m_pStore);
  1097. IxpAssert(m_idIMAPServer != FOLDERID_INVALID);
  1098. // If current folder is already selected, no need to issue SELECT
  1099. if (FOLDERID_INVALID != m_idSelectedFolder &&
  1100. m_idSelectedFolder == m_idFolder)
  1101. {
  1102. hr = STORE_S_NOOP; // Success, but no SELECT command issued
  1103. goto exit;
  1104. }
  1105. if (m_idFolder == FOLDERID_INVALID)
  1106. {
  1107. // noone has called SetFolder on us yet, let's bail
  1108. // with a badfolder error
  1109. hr = TraceResult(STORE_E_BADFOLDERNAME);
  1110. goto exit;
  1111. }
  1112. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, m_idFolder, &pszDestFldrPath,
  1113. NULL, NULL, m_pStore, NULL, NULL);
  1114. if (FAILED(hr))
  1115. {
  1116. TraceResult(hr);
  1117. goto exit;
  1118. }
  1119. // We're about to issue a SELECT command, so clear operation queue
  1120. // (it's filled with commands for previous folder)
  1121. OnFolderExit();
  1122. // Find out what translation mode we should be in
  1123. hr = SetTranslationMode(m_idFolder);
  1124. if (FAILED(hr))
  1125. {
  1126. TraceResult(hr);
  1127. goto exit;
  1128. }
  1129. hr = m_pTransport->Select(tidSELECTION, (LPARAM) m_idFolder, this, pszDestFldrPath);
  1130. if (FAILED(hr))
  1131. {
  1132. TraceResult(hr);
  1133. goto exit;
  1134. }
  1135. exit:
  1136. SafeMemFree(pszDestFldrPath);
  1137. return hr;
  1138. }
  1139. //***************************************************************************
  1140. //***************************************************************************
  1141. HRESULT CIMAPSync::SetIdleCallback(IStoreCallback *pDefaultCallback)
  1142. {
  1143. TraceCall("CIMAPSync::SetOwner");
  1144. AssertSingleThreaded;
  1145. ReplaceInterface(m_pDefCallback, pDefaultCallback);
  1146. return S_OK;
  1147. }
  1148. //***************************************************************************
  1149. //***************************************************************************
  1150. HRESULT CIMAPSync::SetConnectionState(CONNECT_STATE csNewState)
  1151. {
  1152. HRESULT hr;
  1153. TraceCall("CIMAPSync::SetConnectionState");
  1154. AssertSingleThreaded;
  1155. m_csNewConnState = csNewState;
  1156. if (CONNECT_STATE_CONNECT == csNewState)
  1157. {
  1158. hr = _Connect();
  1159. if (FAILED(hr))
  1160. {
  1161. TraceResult(hr);
  1162. goto exit;
  1163. }
  1164. m_fCreateSpecial = TRUE;
  1165. }
  1166. else if (CONNECT_STATE_DISCONNECT == csNewState)
  1167. {
  1168. hr = _Disconnect();
  1169. if (FAILED(hr))
  1170. {
  1171. TraceResult(hr);
  1172. goto exit;
  1173. }
  1174. }
  1175. else
  1176. {
  1177. AssertSz(FALSE, "What do you want?");
  1178. hr = TraceResult(E_INVALIDARG);
  1179. goto exit;
  1180. }
  1181. exit:
  1182. return hr;
  1183. }
  1184. //***************************************************************************
  1185. // Function: SynchronizeFolder
  1186. //
  1187. // Purpose:
  1188. // This function is used to tell CIMAPSync what parts of the message
  1189. // list to synchronize with the IMAP server, and any special sync options.
  1190. // The call is treated as a STANDING ORDER, meaning that if this function
  1191. // is called to get new headers, CIMAPSync assumes that the caller is always
  1192. // interested in new headers. Therefore, the next time the IMAP server informs
  1193. // us of new headers, we download them.
  1194. //***************************************************************************
  1195. HRESULT CIMAPSync::SynchronizeFolder(DWORD dwFlags, DWORD cHeaders, IStoreCallback *pCallback)
  1196. {
  1197. HRESULT hr;
  1198. TraceCall("CIMAPSync::SynchronizeFolder");
  1199. AssertSingleThreaded;
  1200. AssertSz(ISFLAGCLEAR(dwFlags, SYNC_FOLDER_CACHED_HEADERS) ||
  1201. ISFLAGSET(dwFlags, SYNC_FOLDER_NEW_HEADERS),
  1202. "Cannot currently sync cached headers without getting new headers as well");
  1203. IxpAssert(0 == (dwFlags & ~(SYNC_FOLDER_ALLFLAGS)));
  1204. hr = _EnsureInited();
  1205. if (FAILED(hr))
  1206. {
  1207. TraceResult(hr);
  1208. goto exit;
  1209. }
  1210. // Special-case SYNC_FOLDER_PURGE_DELETED. It doesn't really belong here
  1211. // but it allows us to avoid adding a new function to IMessageServer.
  1212. // Do not allow its presence to affect our standing orders
  1213. if (SYNC_FOLDER_PURGE_DELETED & dwFlags)
  1214. {
  1215. // Need to set m_dwSyncFolderFlags with this flag because m_dwSyncToDo gets erased
  1216. Assert(0 == (dwFlags & ~(SYNC_FOLDER_PURGE_DELETED)));
  1217. dwFlags = m_dwSyncFolderFlags | SYNC_FOLDER_PURGE_DELETED;
  1218. }
  1219. m_dwSyncFolderFlags = dwFlags;
  1220. m_dwSyncToDo = dwFlags | SYNC_FOLDER_NOOP;
  1221. m_dwHighestCachedUID = 0;
  1222. exit:
  1223. if (SUCCEEDED(hr))
  1224. hr = _BeginOperation(SOT_SYNC_FOLDER, pCallback);
  1225. return hr;
  1226. }
  1227. //***************************************************************************
  1228. //***************************************************************************
  1229. HRESULT CIMAPSync::GetMessage(MESSAGEID idMessage, IStoreCallback *pCallback)
  1230. {
  1231. HRESULT hr;
  1232. BOOL fNeedMsgSeqNum = FALSE;
  1233. TraceCall("CIMAPSync::GetMessage");
  1234. AssertSingleThreaded;
  1235. IxpAssert(MESSAGEID_INVALID != idMessage);
  1236. hr = _EnsureInited();
  1237. if (FAILED(hr))
  1238. {
  1239. TraceResult(hr);
  1240. goto exit;
  1241. }
  1242. SafeRelease(m_pstmBody);
  1243. hr = CreatePersistentWriteStream(m_pFolder, &m_pstmBody, &m_faStream);
  1244. if (FAILED(hr))
  1245. {
  1246. TraceResult(hr);
  1247. goto exit;
  1248. }
  1249. m_idMessage = idMessage;
  1250. m_fGotBody = FALSE;
  1251. // To FETCH a message we need to translate MsgSeqNum to UID, so check if we can do it
  1252. if (FALSE == ISFLAGSET(m_dwSyncFolderFlags, (SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS)))
  1253. {
  1254. DWORD dwMsgSeqNum;
  1255. HRESULT hrTemp;
  1256. // Both SYNC_FOLDER_NEW_HEADERS and SYNC_FOLDER_CACHED_HEADERS have to be
  1257. // set to guarantee general MsgSeqNumToUID translation. Looks like we may
  1258. // have to get the translation ourselves, but check if we already have it
  1259. hrTemp = ImapUtil_UIDToMsgSeqNum(m_pTransport, (DWORD_PTR)idMessage, &dwMsgSeqNum);
  1260. if (FAILED(hrTemp))
  1261. fNeedMsgSeqNum = TRUE;
  1262. }
  1263. if (fNeedMsgSeqNum)
  1264. {
  1265. char szFetchArgs[50];
  1266. wnsprintfA(szFetchArgs, ARRAYSIZE(szFetchArgs), "%lu (UID)", idMessage);
  1267. hr = _EnqueueOperation(tidBODYMSN, (LPARAM) idMessage, icFETCH_COMMAND,
  1268. szFetchArgs, uiNORMAL_PRIORITY);
  1269. if (FAILED(hr))
  1270. {
  1271. TraceResult(hr);
  1272. goto exit;
  1273. }
  1274. }
  1275. else
  1276. {
  1277. hr = _EnqueueOperation(tidBODY, (LPARAM) idMessage, icFETCH_COMMAND,
  1278. NULL, uiNORMAL_PRIORITY);
  1279. if (FAILED(hr))
  1280. {
  1281. TraceResult(hr);
  1282. goto exit;
  1283. }
  1284. }
  1285. exit:
  1286. if (SUCCEEDED(hr))
  1287. hr = _BeginOperation(SOT_GET_MESSAGE, pCallback);
  1288. return hr;
  1289. }
  1290. //***************************************************************************
  1291. //***************************************************************************
  1292. HRESULT CIMAPSync::PutMessage(FOLDERID idFolder,
  1293. MESSAGEFLAGS dwFlags,
  1294. LPFILETIME pftReceived,
  1295. IStream *pStream,
  1296. IStoreCallback *pCallback)
  1297. {
  1298. HRESULT hr;
  1299. IMAP_MSGFLAGS imfIMAPMsgFlags;
  1300. LPSTR pszDestFldrPath = NULL;
  1301. APPEND_SEND_INFO *pAppendInfo = NULL;
  1302. FOLDERINFO fiFolderInfo;
  1303. BOOL fSuppressRelease = FALSE;
  1304. TraceCall("CIMAPSync::PutMessage");
  1305. AssertSingleThreaded;
  1306. IxpAssert(FOLDERID_INVALID != m_idIMAPServer);
  1307. IxpAssert(NULL != pStream);
  1308. hr = _EnsureInited();
  1309. if (FAILED(hr))
  1310. {
  1311. TraceResult(hr);
  1312. goto exit;
  1313. }
  1314. // Find out what translation mode we should be in
  1315. hr= SetTranslationMode(idFolder);
  1316. if (FAILED(hr))
  1317. {
  1318. TraceResult(hr);
  1319. goto exit;
  1320. }
  1321. // Create a APPEND_SEND_INFO structure
  1322. pAppendInfo = new APPEND_SEND_INFO;
  1323. if (NULL == pAppendInfo)
  1324. {
  1325. hr = TraceResult(E_OUTOFMEMORY);
  1326. goto exit;
  1327. }
  1328. ZeroMemory(pAppendInfo, sizeof(APPEND_SEND_INFO));
  1329. // Fill in the fields
  1330. ImapUtil_LoadRootFldrPrefix(m_pszAccountID, m_szRootFolderPrefix, ARRAYSIZE(m_szRootFolderPrefix));
  1331. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszDestFldrPath, NULL,
  1332. NULL, m_pStore, NULL, m_szRootFolderPrefix);
  1333. if (FAILED(hr))
  1334. {
  1335. TraceResult(hr);
  1336. goto exit;
  1337. }
  1338. // Convert flags to a string
  1339. imfIMAPMsgFlags = DwConvertARFtoIMAP(dwFlags);
  1340. hr = ImapUtil_MsgFlagsToString(imfIMAPMsgFlags, &pAppendInfo->pszMsgFlags, NULL);
  1341. if (FAILED(hr))
  1342. {
  1343. // The show must go on! Default to no IMAP msg flags
  1344. TraceResult(hr);
  1345. pAppendInfo->pszMsgFlags = NULL;
  1346. hr = S_OK; // Suppress error
  1347. }
  1348. // Get a date/time for INTERNALDATE attribute of this msg
  1349. if (NULL == pftReceived)
  1350. {
  1351. SYSTEMTIME stCurrentTime;
  1352. // Substitute current date/time
  1353. GetSystemTime(&stCurrentTime);
  1354. SystemTimeToFileTime(&stCurrentTime, &pAppendInfo->ftReceived);
  1355. }
  1356. else
  1357. pAppendInfo->ftReceived = *pftReceived;
  1358. pAppendInfo->lpstmMsg = pStream;
  1359. pStream->AddRef();
  1360. // Check for the case where destination is a special folder whose creation was deferred
  1361. hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
  1362. if (SUCCEEDED(hr))
  1363. {
  1364. if (FOLDER_CREATEONDEMAND & fiFolderInfo.dwFlags)
  1365. {
  1366. CREATE_FOLDER_INFO *pcfi;
  1367. Assert(FOLDER_NOTSPECIAL != fiFolderInfo.tySpecial);
  1368. pcfi = new CREATE_FOLDER_INFO;
  1369. if (NULL == pcfi)
  1370. {
  1371. hr = TraceResult(E_OUTOFMEMORY);
  1372. goto exit;
  1373. }
  1374. // Fill in all the fields
  1375. pcfi->pszFullFolderPath = PszDupA(pszDestFldrPath);
  1376. if (NULL == pcfi->pszFullFolderPath)
  1377. {
  1378. hr = TraceResult(E_OUTOFMEMORY);
  1379. goto exit;
  1380. }
  1381. pcfi->idFolder = FOLDERID_INVALID;
  1382. pcfi->dwFlags = 0;
  1383. pcfi->csfCurrentStage = CSF_INIT;
  1384. pcfi->dwCurrentSfType = fiFolderInfo.tySpecial;
  1385. pcfi->dwFinalSfType = fiFolderInfo.tySpecial;
  1386. pcfi->lParam = (LPARAM) pAppendInfo;
  1387. pcfi->pcoNextOp = PCO_APPENDMSG;
  1388. hr = CreateNextSpecialFolder(pcfi, NULL);
  1389. TraceError(hr); // CreateNextSpecialFolder deletes pcfi on its own if it fails
  1390. fSuppressRelease = TRUE; // It also frees pAppendInfo if it fails
  1391. m_pStore->FreeRecord(&fiFolderInfo);
  1392. goto exit; // Don't send APPEND command until after entire CREATE operation
  1393. }
  1394. m_pStore->FreeRecord(&fiFolderInfo);
  1395. }
  1396. // We're ready to send the APPEND command!
  1397. hr = _EnqueueOperation(tidUPLOADMSG, (LPARAM) pAppendInfo, icAPPEND_COMMAND,
  1398. pszDestFldrPath, uiNORMAL_PRIORITY);
  1399. if (FAILED(hr))
  1400. {
  1401. TraceResult(hr);
  1402. goto exit;
  1403. }
  1404. exit:
  1405. if (NULL != pszDestFldrPath)
  1406. MemFree(pszDestFldrPath);
  1407. if (SUCCEEDED(hr))
  1408. hr = _BeginOperation(SOT_PUT_MESSAGE, pCallback);
  1409. else if (NULL != pAppendInfo && FALSE == fSuppressRelease)
  1410. {
  1411. SafeMemFree(pAppendInfo->pszMsgFlags);
  1412. delete pAppendInfo;
  1413. }
  1414. return hr;
  1415. }
  1416. //***************************************************************************
  1417. //***************************************************************************
  1418. HRESULT CIMAPSync::SetMessageFlags(LPMESSAGEIDLIST pList,
  1419. LPADJUSTFLAGS pFlags,
  1420. SETMESSAGEFLAGSFLAGS dwFlags,
  1421. IStoreCallback *pCallback)
  1422. {
  1423. HRESULT hr;
  1424. TraceCall("CIMAPSync::SetMessageFlags");
  1425. AssertSingleThreaded;
  1426. IxpAssert(m_cRef > 0);
  1427. IxpAssert(NULL == pList || pList->cMsgs > 0);
  1428. hr = _EnsureInited();
  1429. if (FAILED(hr))
  1430. {
  1431. TraceResult(hr);
  1432. goto exit;
  1433. }
  1434. hr = _SetMessageFlags(SOT_SET_MESSAGEFLAGS, pList, pFlags, pCallback);
  1435. if (FAILED(hr))
  1436. {
  1437. TraceResult(hr);
  1438. goto exit;
  1439. }
  1440. exit:
  1441. if (SUCCEEDED(hr))
  1442. hr = _BeginOperation(SOT_SET_MESSAGEFLAGS, pCallback);
  1443. return hr;
  1444. }
  1445. HRESULT CIMAPSync::GetServerMessageFlags(MESSAGEFLAGS *pFlags)
  1446. {
  1447. *pFlags = DwConvertIMAPtoARF(IMAP_MSG_ALLFLAGS);
  1448. return S_OK;
  1449. }
  1450. //***************************************************************************
  1451. // Helper function to mark messages
  1452. //***************************************************************************
  1453. HRESULT CIMAPSync::_SetMessageFlags(STOREOPERATIONTYPE sotOpType,
  1454. LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags,
  1455. IStoreCallback *pCallback)
  1456. {
  1457. HRESULT hr;
  1458. MARK_MSGS_INFO *pMARK_MSGS_INFO = NULL;
  1459. char szFlagArgs[200];
  1460. LPSTR pszFlagList;
  1461. DWORD dwLen;
  1462. LPSTR p;
  1463. IMAP_MSGFLAGS imfFlags;
  1464. DWORD dw;
  1465. ULONG ul;
  1466. TraceCall("CIMAPSync::_SetMessageFlags");
  1467. AssertSingleThreaded;
  1468. IxpAssert(NULL == pList || pList->cMsgs > 0);
  1469. // Construct a mark msg operation
  1470. // Check the requested flag adjustments
  1471. if (0 == pFlags->dwRemove && 0 == pFlags->dwAdd)
  1472. {
  1473. // Nothing to do here, exit with a smile
  1474. hr = S_OK;
  1475. goto exit;
  1476. }
  1477. if ((0 != pFlags->dwRemove && 0 != pFlags->dwAdd) ||
  1478. (0 != (pFlags->dwRemove & pFlags->dwAdd)))
  1479. {
  1480. // IMAP can't do any of the following:
  1481. // 1) add and removal of flags at the same time (NYI: takes 2 STORE cmds)
  1482. // 2) add/removal of same flag (makes no sense)
  1483. hr = TraceResult(E_INVALIDARG);
  1484. goto exit;
  1485. }
  1486. // If ARF_ENDANGERED is set, be sure to set ARF_READ so we don't mess up
  1487. // unread counts as returned by STATUS command
  1488. if (pFlags->dwAdd & ARF_ENDANGERED)
  1489. pFlags->dwAdd |= ARF_READ;
  1490. // Construct MARK_MSGS_INFO structure
  1491. pMARK_MSGS_INFO = new MARK_MSGS_INFO;
  1492. if (NULL == pMARK_MSGS_INFO)
  1493. {
  1494. hr = TraceResult(E_OUTOFMEMORY);
  1495. goto exit;
  1496. }
  1497. ZeroMemory(pMARK_MSGS_INFO, sizeof(MARK_MSGS_INFO));
  1498. // Create a rangelist
  1499. hr = CreateRangeList(&pMARK_MSGS_INFO->pMsgRange);
  1500. if (FAILED(hr))
  1501. {
  1502. TraceResult(hr);
  1503. goto exit;
  1504. }
  1505. // Remember these args so we can set the message flags after server confirmation
  1506. pMARK_MSGS_INFO->afFlags = *pFlags;
  1507. hr = CloneMessageIDList(pList, &pMARK_MSGS_INFO->pList);
  1508. if (FAILED(hr))
  1509. {
  1510. TraceResult(hr);
  1511. goto exit;
  1512. }
  1513. pMARK_MSGS_INFO->sotOpType = sotOpType;
  1514. // Get arguments for the STORE command
  1515. if (0 != pFlags->dwRemove)
  1516. szFlagArgs[0] = '-';
  1517. else
  1518. szFlagArgs[0] = '+';
  1519. p = szFlagArgs + 1;
  1520. p += wnsprintf(p, (ARRAYSIZE(szFlagArgs) - 1), "FLAGS.SILENT ");
  1521. imfFlags = DwConvertARFtoIMAP(pFlags->dwRemove ? pFlags->dwRemove : pFlags->dwAdd);
  1522. hr = ImapUtil_MsgFlagsToString(imfFlags, &pszFlagList, &dwLen);
  1523. if (FAILED(hr))
  1524. {
  1525. TraceResult(hr);
  1526. goto exit;
  1527. }
  1528. if (hr != S_FALSE)
  1529. {
  1530. IxpAssert(dwLen < (sizeof(szFlagArgs) - (p - szFlagArgs)));
  1531. StrCpyN(p, pszFlagList, ARRAYSIZE(szFlagArgs) - (int) (p - szFlagArgs));
  1532. MemFree(pszFlagList);
  1533. }
  1534. // Convert IDList to rangelist to submit to IIMAPTransport
  1535. if (NULL != pList)
  1536. {
  1537. for (dw = 0; dw < pList->cMsgs; dw++)
  1538. {
  1539. HRESULT hrTemp;
  1540. hrTemp = pMARK_MSGS_INFO->pMsgRange->AddSingleValue(PtrToUlong(pList->prgidMsg[dw]));
  1541. TraceError(hrTemp);
  1542. }
  1543. }
  1544. else
  1545. {
  1546. HRESULT hrTemp;
  1547. // pList == NULL means to tackle ALL messages
  1548. hrTemp = pMARK_MSGS_INFO->pMsgRange->AddRange(1, RL_LAST_MESSAGE);
  1549. TraceError(hrTemp);
  1550. }
  1551. IxpAssert(SUCCEEDED(pMARK_MSGS_INFO->pMsgRange->Cardinality(&ul)) && ul > 0);
  1552. // Send the command! (At last!)
  1553. hr = _EnqueueOperation(tidMARKMSGS, (LPARAM) pMARK_MSGS_INFO, icSTORE_COMMAND,
  1554. szFlagArgs, uiNORMAL_PRIORITY);
  1555. if (FAILED(hr))
  1556. {
  1557. TraceResult(hr);
  1558. goto exit;
  1559. }
  1560. exit:
  1561. return hr;
  1562. }
  1563. //***************************************************************************
  1564. //***************************************************************************
  1565. HRESULT CIMAPSync::CopyMessages(IMessageFolder *pDestFldr,
  1566. COPYMESSAGEFLAGS dwOptions,
  1567. LPMESSAGEIDLIST pList,
  1568. LPADJUSTFLAGS pFlags,
  1569. IStoreCallback *pCallback)
  1570. {
  1571. HRESULT hr;
  1572. FOLDERID idDestFldr;
  1573. FOLDERINFO fiFolderInfo;
  1574. BOOL fFreeFldrInfo = FALSE;
  1575. CHAR szAccountId[CCHMAX_ACCOUNT_NAME];
  1576. TraceCall("CIMAPSync::CopyMoveMessages");
  1577. AssertSingleThreaded;
  1578. IxpAssert(FOLDERID_INVALID != m_idIMAPServer);
  1579. hr = _EnsureInited();
  1580. if (FAILED(hr))
  1581. {
  1582. TraceResult(hr);
  1583. goto exit;
  1584. }
  1585. // Check if we can do an IMAP COPY command to satisfy this copy request
  1586. hr = pDestFldr->GetFolderId(&idDestFldr);
  1587. if (FAILED(hr))
  1588. {
  1589. TraceResult(hr);
  1590. goto exit;
  1591. }
  1592. hr = m_pStore->GetFolderInfo(idDestFldr, &fiFolderInfo);
  1593. if (FAILED(hr))
  1594. {
  1595. TraceResult(hr);
  1596. goto exit;
  1597. }
  1598. fFreeFldrInfo = TRUE;
  1599. GetFolderAccountId(&fiFolderInfo, szAccountId, ARRAYSIZE(szAccountId));
  1600. if (0 == lstrcmpi(m_pszAccountID, szAccountId) && FOLDER_IMAP == fiFolderInfo.tyFolder)
  1601. {
  1602. IMAP_COPYMOVE_INFO *pCopyInfo;
  1603. LPSTR pszDestFldrPath;
  1604. DWORD dw;
  1605. ULONG ul;
  1606. // This copy can be accomplished with an IMAP copy command!
  1607. // Check args
  1608. if (NULL != pFlags && (0 != pFlags->dwAdd || 0 != pFlags->dwRemove))
  1609. // IMAP cannot set the flags of copied msg. We would either have to set
  1610. // flags on source before copying, or go to destination folder and set flags
  1611. TraceResult(E_INVALIDARG); // Record error but continue (error not fatal)
  1612. // Find out what translation mode we should be in
  1613. hr = SetTranslationMode(idDestFldr);
  1614. if (FAILED(hr))
  1615. {
  1616. TraceResult(hr);
  1617. goto exit;
  1618. }
  1619. // Construct CopyMoveInfo structure
  1620. pCopyInfo = new IMAP_COPYMOVE_INFO;
  1621. if (NULL == pCopyInfo)
  1622. {
  1623. hr = TraceResult(E_OUTOFMEMORY);
  1624. goto exit;
  1625. }
  1626. pCopyInfo->dwOptions = dwOptions;
  1627. pCopyInfo->idDestFldr = idDestFldr;
  1628. hr = CloneMessageIDList(pList, &pCopyInfo->pList);
  1629. if (FAILED(hr))
  1630. {
  1631. TraceResult(hr);
  1632. goto exit;
  1633. }
  1634. hr = CreateRangeList(&pCopyInfo->pCopyRange);
  1635. if (FAILED(hr))
  1636. {
  1637. TraceResult(hr);
  1638. goto exit;
  1639. }
  1640. // Convert IDList to rangelist to submit to IIMAPTransport
  1641. if (NULL != pList)
  1642. {
  1643. for (dw = 0; dw < pList->cMsgs; dw++)
  1644. {
  1645. HRESULT hrTemp;
  1646. hrTemp = pCopyInfo->pCopyRange->AddSingleValue(PtrToUlong(pList->prgidMsg[dw]));
  1647. TraceError(hrTemp);
  1648. }
  1649. }
  1650. else
  1651. {
  1652. HRESULT hrTemp;
  1653. // pList == NULL means to tackle ALL messages
  1654. hrTemp = pCopyInfo->pCopyRange->AddRange(1, RL_LAST_MESSAGE);
  1655. TraceError(hrTemp);
  1656. }
  1657. IxpAssert(SUCCEEDED(pCopyInfo->pCopyRange->Cardinality(&ul)) && ul > 0);
  1658. // Construct destination folder path
  1659. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idDestFldr, &pszDestFldrPath,
  1660. NULL, NULL, m_pStore, NULL, NULL);
  1661. if (FAILED(hr))
  1662. {
  1663. TraceResult(hr);
  1664. goto exit;
  1665. }
  1666. // Send command to server
  1667. hr = _EnqueueOperation(tidCOPYMSGS, (LPARAM) pCopyInfo, icCOPY_COMMAND,
  1668. pszDestFldrPath, uiNORMAL_PRIORITY);
  1669. MemFree(pszDestFldrPath);
  1670. if (FAILED(hr))
  1671. {
  1672. TraceResult(hr);
  1673. goto exit;
  1674. }
  1675. }
  1676. else
  1677. {
  1678. // This is a standard (download from src-save to dest) copy: let caller do it
  1679. hr = STORE_E_NOSERVERCOPY;
  1680. goto exit; // Don't record this error value, it's expected
  1681. }
  1682. exit:
  1683. if (fFreeFldrInfo)
  1684. m_pStore->FreeRecord(&fiFolderInfo);
  1685. if (SUCCEEDED(hr))
  1686. hr = _BeginOperation(SOT_COPYMOVE_MESSAGE, pCallback);
  1687. return hr;
  1688. }
  1689. //***************************************************************************
  1690. //***************************************************************************
  1691. HRESULT CIMAPSync::DeleteMessages(DELETEMESSAGEFLAGS dwOptions,
  1692. LPMESSAGEIDLIST pList,
  1693. IStoreCallback *pCallback)
  1694. {
  1695. ADJUSTFLAGS afFlags;
  1696. HRESULT hr;
  1697. TraceCall("CIMAPSync::DeleteMessages");
  1698. AssertSingleThreaded;
  1699. IxpAssert(NULL == pList || pList->cMsgs > 0);
  1700. // This function currently only supports IMAP deletion model. Trashcan NYI.
  1701. hr = _EnsureInited();
  1702. if (FAILED(hr))
  1703. {
  1704. TraceResult(hr);
  1705. goto exit;
  1706. }
  1707. if (dwOptions & DELETE_MESSAGE_UNDELETE)
  1708. {
  1709. afFlags.dwAdd = 0;
  1710. afFlags.dwRemove = ARF_ENDANGERED;
  1711. }
  1712. else
  1713. {
  1714. afFlags.dwAdd = ARF_ENDANGERED;
  1715. afFlags.dwRemove = 0;
  1716. }
  1717. hr = _SetMessageFlags(SOT_DELETING_MESSAGES, pList, &afFlags, pCallback);
  1718. if (FAILED(hr))
  1719. {
  1720. TraceError(hr);
  1721. goto exit;
  1722. }
  1723. exit:
  1724. if (SUCCEEDED(hr))
  1725. hr = _BeginOperation(SOT_DELETING_MESSAGES, pCallback);
  1726. return hr;
  1727. }
  1728. //***************************************************************************
  1729. //***************************************************************************
  1730. HRESULT CIMAPSync::SynchronizeStore(FOLDERID idParent,
  1731. DWORD dwFlags,
  1732. IStoreCallback *pCallback)
  1733. {
  1734. HRESULT hr = S_OK;
  1735. TraceCall("CIMAPSync::SynchronizeStore");
  1736. AssertSingleThreaded;
  1737. IxpAssert(SOT_INVALID == m_sotCurrent);
  1738. IxpAssert(NULL == m_pCurrentCB);
  1739. // This function currently ignores the dwFlags argument
  1740. m_cFolders = 0;
  1741. hr = _EnsureInited();
  1742. if (FAILED(hr))
  1743. {
  1744. TraceResult(hr);
  1745. goto exit;
  1746. }
  1747. // Force mailbox translation since we only issue LIST *
  1748. hr = SetTranslationMode(FOLDERID_INVALID);
  1749. if (FAILED(hr))
  1750. {
  1751. TraceResult(hr);
  1752. goto exit;
  1753. }
  1754. SafeRelease(m_pCurrentHash);
  1755. SafeRelease(m_pListHash);
  1756. m_sotCurrent = SOT_SYNCING_STORE;
  1757. m_pCurrentCB = pCallback;
  1758. if (NULL != pCallback)
  1759. pCallback->AddRef();
  1760. hr = CreateFolderHash(m_pStore, m_idIMAPServer, &m_pCurrentHash);
  1761. if (FAILED(hr))
  1762. {
  1763. TraceResult(hr);
  1764. goto exit;
  1765. }
  1766. hr = MimeOleCreateHashTable(CHASH_BUCKETS, TRUE, &m_pListHash);
  1767. if (FAILED(hr))
  1768. {
  1769. TraceResult(hr);
  1770. goto exit;
  1771. }
  1772. if (INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar)
  1773. {
  1774. // Set us up to find out root hierarchy char
  1775. m_phcfHierarchyCharInfo = new HIERARCHY_CHAR_FINDER;
  1776. if (NULL == m_phcfHierarchyCharInfo)
  1777. {
  1778. hr = TraceResult(E_OUTOFMEMORY);
  1779. goto exit;
  1780. }
  1781. ZeroMemory(m_phcfHierarchyCharInfo, sizeof(HIERARCHY_CHAR_FINDER));
  1782. }
  1783. ImapUtil_LoadRootFldrPrefix(m_pszAccountID, m_szRootFolderPrefix, ARRAYSIZE(m_szRootFolderPrefix));
  1784. hr = _StartFolderList((LPARAM)FOLDERID_INVALID);
  1785. if (FAILED(hr))
  1786. {
  1787. TraceResult(hr);
  1788. goto exit;
  1789. }
  1790. exit:
  1791. if (FAILED(hr))
  1792. {
  1793. m_sotCurrent = SOT_INVALID;
  1794. m_pCurrentCB = NULL;
  1795. m_fTerminating = FALSE;
  1796. if (NULL != pCallback)
  1797. pCallback->Release();
  1798. }
  1799. else
  1800. hr = _BeginOperation(m_sotCurrent, m_pCurrentCB);
  1801. return hr;
  1802. } // SynchronizeStore
  1803. //***************************************************************************
  1804. //***************************************************************************
  1805. HRESULT CIMAPSync::CreateFolder(FOLDERID idParent,
  1806. SPECIALFOLDER tySpecial,
  1807. LPCSTR pszName,
  1808. FLDRFLAGS dwFlags,
  1809. IStoreCallback *pCallback)
  1810. {
  1811. HRESULT hr;
  1812. CHAR chHierarchy;
  1813. LPSTR pszFullPath=NULL;
  1814. CREATE_FOLDER_INFO *pcfi=NULL;
  1815. DWORD dwFullPathLen;
  1816. LPSTR pszEnd;
  1817. TraceCall("CIMAPSync::CreateFolder");
  1818. AssertSingleThreaded;
  1819. hr = _EnsureInited();
  1820. if (FAILED(hr))
  1821. {
  1822. TraceResult(hr);
  1823. goto exit;
  1824. }
  1825. // Validate folder name
  1826. hr = CheckFolderNameValidity(pszName);
  1827. if (FAILED(hr))
  1828. {
  1829. TraceResult(hr);
  1830. goto exit;
  1831. }
  1832. // Find out what translation mode we should be in
  1833. hr = SetTranslationMode(idParent);
  1834. if (FAILED(hr))
  1835. {
  1836. TraceResult(hr);
  1837. goto exit;
  1838. }
  1839. else if (S_FALSE == hr)
  1840. {
  1841. // Parent not translatable from UTF7. In such a case, we only allow creation
  1842. // if child foldername is composed ENTIRELY of USASCII
  1843. if (FALSE == isUSASCIIOnly(pszName))
  1844. {
  1845. // We can't create this folder: parent prohibits UTF7 translation
  1846. hr = TraceResult(STORE_E_NOTRANSLATION);
  1847. goto exit;
  1848. }
  1849. }
  1850. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idParent, &pszFullPath, &dwFullPathLen,
  1851. &chHierarchy, m_pStore, pszName, NULL);
  1852. if (FAILED(hr))
  1853. {
  1854. TraceResult(hr);
  1855. goto exit;
  1856. }
  1857. pcfi = new CREATE_FOLDER_INFO;
  1858. if (NULL == pcfi)
  1859. {
  1860. hr = TraceResult(E_OUTOFMEMORY);
  1861. goto exit;
  1862. }
  1863. // Fill in all the fields
  1864. pcfi->pszFullFolderPath = pszFullPath;
  1865. pcfi->idFolder = FOLDERID_INVALID;
  1866. pcfi->dwFlags = 0;
  1867. pcfi->csfCurrentStage = CSF_INIT;
  1868. pcfi->dwCurrentSfType = FOLDER_NOTSPECIAL;
  1869. pcfi->dwFinalSfType = FOLDER_NOTSPECIAL;
  1870. pcfi->lParam = NULL;
  1871. pcfi->pcoNextOp = PCO_NONE;
  1872. // Send the CREATE command
  1873. hr = _EnqueueOperation(tidCREATE, (LPARAM)pcfi, icCREATE_COMMAND, pszFullPath, uiNORMAL_PRIORITY);
  1874. if (FAILED(hr))
  1875. {
  1876. TraceResult(hr);
  1877. goto exit;
  1878. }
  1879. // If there is a trailing HC (required to create folder-bearing folders on UW IMAP),
  1880. // remove it from pszFullPath so that LIST and SUBSCRIBE do not carry it (IE5 bug #60054)
  1881. pszEnd = CharPrev(pszFullPath, pszFullPath + dwFullPathLen);
  1882. if (chHierarchy == *pszEnd)
  1883. {
  1884. *pszEnd = '\0';
  1885. Assert(*CharPrev(pszFullPath, pszEnd) != chHierarchy); // Shouldn't get > 1 HC at end
  1886. }
  1887. exit:
  1888. if (FAILED(hr))
  1889. {
  1890. SafeMemFree(pszFullPath);
  1891. delete pcfi;
  1892. }
  1893. else
  1894. hr = _BeginOperation(SOT_CREATE_FOLDER, pCallback);
  1895. return hr;
  1896. }
  1897. //***************************************************************************
  1898. //***************************************************************************
  1899. HRESULT CIMAPSync::MoveFolder(FOLDERID idFolder,
  1900. FOLDERID idParentNew,
  1901. IStoreCallback *pCallback)
  1902. {
  1903. HRESULT hr;
  1904. TraceCall("CIMAPSync::MoveFolder");
  1905. AssertSingleThreaded;
  1906. hr = _EnsureInited();
  1907. if (FAILED(hr))
  1908. {
  1909. TraceResult(hr);
  1910. goto exit;
  1911. }
  1912. hr = TraceResult(E_NOTIMPL);
  1913. exit:
  1914. if (SUCCEEDED(hr))
  1915. hr = _BeginOperation(SOT_MOVE_FOLDER, pCallback);
  1916. return hr;
  1917. }
  1918. //***************************************************************************
  1919. //***************************************************************************
  1920. HRESULT CIMAPSync::RenameFolder(FOLDERID idFolder,
  1921. LPCSTR pszName,
  1922. IStoreCallback *pCallback)
  1923. {
  1924. HRESULT hr;
  1925. FOLDERINFO fiFolderInfo;
  1926. LPSTR pszOldPath = NULL;
  1927. LPSTR pszNewPath = NULL;
  1928. char chHierarchy;
  1929. BOOL fFreeInfo = FALSE;
  1930. TraceCall("CIMAPSync::RenameFolder");
  1931. AssertSingleThreaded;
  1932. hr = _EnsureInited();
  1933. if (FAILED(hr))
  1934. {
  1935. TraceResult(hr);
  1936. goto exit;
  1937. }
  1938. // Validate folder name
  1939. hr = CheckFolderNameValidity(pszName);
  1940. if (FAILED(hr))
  1941. {
  1942. TraceResult(hr);
  1943. goto exit;
  1944. }
  1945. // Find out what translation mode we should be in
  1946. hr = SetTranslationMode(idFolder);
  1947. if (FAILED(hr))
  1948. {
  1949. TraceResult(hr);
  1950. goto exit;
  1951. }
  1952. else if (S_FALSE == hr)
  1953. {
  1954. // Folder not translatable from UTF7. In such a case, we only allow creation
  1955. // if new foldername is composed ENTIRELY of USASCII. A bit conservative, yes
  1956. // (if leaf node is only un-translatable part, we COULD rename), but I'm too
  1957. // lazy to check for FOLDER_NOTRANSLATEUTF7 all the way to server node.
  1958. if (FALSE == isUSASCIIOnly(pszName))
  1959. {
  1960. // We can't rename this folder: we assume parents prohibit UTF7 translation
  1961. hr = TraceResult(STORE_E_NOTRANSLATION);
  1962. goto exit;
  1963. }
  1964. }
  1965. hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
  1966. if (FAILED(hr))
  1967. {
  1968. TraceResult(hr);
  1969. goto exit;
  1970. }
  1971. // Check validity
  1972. fFreeInfo = TRUE;
  1973. IxpAssert(FOLDER_NOTSPECIAL == fiFolderInfo.tySpecial);
  1974. IxpAssert('\0' != fiFolderInfo.pszName);
  1975. IxpAssert('\0' != pszName);
  1976. if (0 == lstrcmp(fiFolderInfo.pszName, pszName))
  1977. {
  1978. hr = E_INVALIDARG; // Nothing to do! Return error.
  1979. goto exit;
  1980. }
  1981. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszOldPath, NULL,
  1982. &chHierarchy, m_pStore, NULL, NULL);
  1983. if (FAILED(hr))
  1984. {
  1985. TraceResult(hr);
  1986. goto exit;
  1987. }
  1988. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, fiFolderInfo.idParent, &pszNewPath,
  1989. NULL, &chHierarchy, m_pStore, pszName, NULL);
  1990. if (FAILED(hr))
  1991. {
  1992. TraceResult(hr);
  1993. goto exit;
  1994. }
  1995. hr = RenameFolderHelper(idFolder, pszOldPath, chHierarchy, pszNewPath);
  1996. if (FAILED(hr))
  1997. {
  1998. TraceResult(hr);
  1999. goto exit;
  2000. }
  2001. exit:
  2002. if (SUCCEEDED(hr))
  2003. hr = _BeginOperation(SOT_RENAME_FOLDER, pCallback);
  2004. SafeMemFree(pszOldPath);
  2005. SafeMemFree(pszNewPath);
  2006. if (fFreeInfo)
  2007. m_pStore->FreeRecord(&fiFolderInfo);
  2008. return hr;
  2009. }
  2010. //***************************************************************************
  2011. //***************************************************************************
  2012. HRESULT CIMAPSync::DeleteFolder(FOLDERID idFolder,
  2013. DELETEFOLDERFLAGS dwFlags,
  2014. IStoreCallback *pCallback)
  2015. {
  2016. HRESULT hr;
  2017. DELETE_FOLDER_INFO *pdfi = NULL;
  2018. LPSTR pszPath = NULL;
  2019. CHAR chHierarchy;
  2020. TraceCall("CIMAPSync::DeleteFolder");
  2021. AssertSingleThreaded;
  2022. hr = _EnsureInited();
  2023. if (FAILED(hr))
  2024. {
  2025. TraceResult(hr);
  2026. goto exit;
  2027. }
  2028. // Find out what translation mode we should be in
  2029. hr = SetTranslationMode(idFolder);
  2030. if (FAILED(hr))
  2031. {
  2032. TraceResult(hr);
  2033. goto exit;
  2034. }
  2035. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszPath, NULL,
  2036. &chHierarchy, m_pStore, NULL, NULL);
  2037. if (FAILED(hr))
  2038. {
  2039. TraceResult(hr);
  2040. goto exit;
  2041. }
  2042. // Create a CreateFolderInfo structure
  2043. if (!MemAlloc((LPVOID *)&pdfi, sizeof(DELETE_FOLDER_INFO)))
  2044. {
  2045. hr = TraceResult(E_OUTOFMEMORY);
  2046. goto exit;
  2047. }
  2048. pdfi->pszFullFolderPath = pszPath;
  2049. pdfi->cHierarchyChar = chHierarchy;
  2050. pdfi->idFolder = idFolder;
  2051. // Send the DELETE command
  2052. hr = _EnqueueOperation(tidDELETEFLDR, (LPARAM)pdfi, icDELETE_COMMAND, pszPath, uiNORMAL_PRIORITY);
  2053. if (FAILED(hr))
  2054. {
  2055. TraceResult(hr);
  2056. goto exit;
  2057. }
  2058. exit:
  2059. if (FAILED(hr))
  2060. {
  2061. SafeMemFree(pszPath);
  2062. SafeMemFree(pdfi);
  2063. }
  2064. else
  2065. hr = _BeginOperation(SOT_DELETE_FOLDER, pCallback);
  2066. return hr;
  2067. }
  2068. //***************************************************************************
  2069. //***************************************************************************
  2070. HRESULT CIMAPSync::SubscribeToFolder(FOLDERID idFolder,
  2071. BOOL fSubscribe,
  2072. IStoreCallback *pCallback)
  2073. {
  2074. HRESULT hr;
  2075. LPSTR pszPath = NULL;
  2076. TraceCall("CIMAPSync::SubscribeToFolder");
  2077. AssertSingleThreaded;
  2078. IxpAssert(FOLDERID_INVALID == m_idCurrent);
  2079. IxpAssert(FALSE == m_fSubscribe);
  2080. hr = _EnsureInited();
  2081. if (FAILED(hr))
  2082. {
  2083. TraceResult(hr);
  2084. goto exit;
  2085. }
  2086. // Find out what translation mode we should be in
  2087. hr = SetTranslationMode(idFolder);
  2088. if (FAILED(hr))
  2089. {
  2090. TraceResult(hr);
  2091. goto exit;
  2092. }
  2093. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszPath, NULL, NULL,
  2094. m_pStore, NULL, NULL);
  2095. if (FAILED(hr))
  2096. {
  2097. TraceResult(hr);
  2098. goto exit;
  2099. }
  2100. // Send the SUBSCRIBE/UNSUBSCRIBE command
  2101. m_idCurrent = idFolder;
  2102. m_fSubscribe = fSubscribe;
  2103. hr = _EnqueueOperation(tidSUBSCRIBE, 0, fSubscribe ? icSUBSCRIBE_COMMAND :
  2104. icUNSUBSCRIBE_COMMAND, pszPath, uiNORMAL_PRIORITY);
  2105. if (FAILED(hr))
  2106. {
  2107. TraceResult(hr);
  2108. goto exit;
  2109. }
  2110. exit:
  2111. SafeMemFree(pszPath);
  2112. if (SUCCEEDED(hr))
  2113. hr = _BeginOperation(SOT_SUBSCRIBE_FOLDER, pCallback);
  2114. return hr;
  2115. }
  2116. //***************************************************************************
  2117. //***************************************************************************
  2118. HRESULT CIMAPSync::GetFolderCounts(FOLDERID idFolder, IStoreCallback *pCallback)
  2119. {
  2120. HRESULT hr;
  2121. LPSTR pszPath = NULL;
  2122. DWORD dwCapabilities;
  2123. FOLDERINFO fiFolderInfo;
  2124. BOOL fFreeFldrInfo = FALSE;
  2125. TraceCall("CIMAPSync::GetFolderCounts");
  2126. AssertSingleThreaded;
  2127. IxpAssert(FOLDERID_INVALID != idFolder);
  2128. IxpAssert(NULL != pCallback);
  2129. hr = _EnsureInited();
  2130. if (FAILED(hr))
  2131. {
  2132. TraceResult(hr);
  2133. goto exit;
  2134. }
  2135. // Find out what translation mode we should be in
  2136. hr = SetTranslationMode(idFolder);
  2137. if (FAILED(hr))
  2138. {
  2139. TraceResult(hr);
  2140. goto exit;
  2141. }
  2142. // Perform some verification: folder cannot be \NoSelect, server must be IMAP4rev1
  2143. // Unfortunately, we can't get capability unless we're currently connected
  2144. hr = m_pTransport->IsState(IXP_IS_AUTHENTICATED);
  2145. if (S_OK == hr)
  2146. {
  2147. hr = m_pTransport->Capability(&dwCapabilities);
  2148. if (SUCCEEDED(hr) && 0 == (dwCapabilities & IMAP_CAPABILITY_IMAP4rev1))
  2149. {
  2150. // This server does not support STATUS command, we don't support alternate
  2151. // method of unread count update (eg, EXAMINE folder)
  2152. hr = E_NOTIMPL;
  2153. goto exit;
  2154. }
  2155. }
  2156. // If not connected then we'll check capabilities during connection
  2157. hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
  2158. if (SUCCEEDED(hr))
  2159. {
  2160. fFreeFldrInfo = TRUE;
  2161. if (fiFolderInfo.dwFlags & (FOLDER_NOSELECT | FOLDER_NONEXISTENT))
  2162. {
  2163. // This folder cannot have an unread count because it cannot contain messages
  2164. hr = TraceResult(STORE_E_NOSERVERSUPPORT);
  2165. goto exit;
  2166. }
  2167. }
  2168. hr = ImapUtil_FolderIDToPath(m_idIMAPServer, idFolder, &pszPath, NULL, NULL,
  2169. m_pStore, NULL, NULL);
  2170. if (FAILED(hr))
  2171. {
  2172. TraceResult(hr);
  2173. goto exit;
  2174. }
  2175. ImapUtil_LoadRootFldrPrefix(m_pszAccountID, m_szRootFolderPrefix, ARRAYSIZE(m_szRootFolderPrefix));
  2176. hr = LoadSaveRootHierarchyChar(fLOAD_HC);
  2177. if (FAILED(hr))
  2178. {
  2179. TraceResult(hr);
  2180. goto exit;
  2181. }
  2182. // Send the STATUS command
  2183. hr = _EnqueueOperation(tidSTATUS, (LPARAM)idFolder, icSTATUS_COMMAND, pszPath, uiNORMAL_PRIORITY);
  2184. if (FAILED(hr))
  2185. {
  2186. TraceResult(hr);
  2187. goto exit;
  2188. }
  2189. exit:
  2190. SafeMemFree(pszPath);
  2191. if (fFreeFldrInfo)
  2192. m_pStore->FreeRecord(&fiFolderInfo);
  2193. if (SUCCEEDED(hr))
  2194. hr = _BeginOperation(SOT_UPDATE_FOLDER, pCallback);
  2195. return hr;
  2196. }
  2197. STDMETHODIMP CIMAPSync::GetNewGroups(LPSYSTEMTIME pSysTime, IStoreCallback *pCallback)
  2198. {
  2199. return E_NOTIMPL;
  2200. }
  2201. HRESULT STDMETHODCALLTYPE CIMAPSync::ExpungeOnExit(void)
  2202. {
  2203. HWND hwndParent;
  2204. HRESULT hrResult = S_OK;
  2205. // Check if user wants us to purge on exit (only if no operations in progress)
  2206. if (DwGetOption(OPT_IMAPPURGE))
  2207. {
  2208. hrResult = GetParentWindow(0, &hwndParent);
  2209. if (SUCCEEDED(hrResult))
  2210. {
  2211. hrResult = PurgeMessageProgress(hwndParent);
  2212. TraceError(hrResult);
  2213. }
  2214. }
  2215. return hrResult;
  2216. } // ExpungeOnExit
  2217. HRESULT CIMAPSync::Cancel(CANCELTYPE tyCancel)
  2218. {
  2219. // $TODO: Translate tyCancel into an HRESULT to return to the caller
  2220. FlushOperationQueue(issNotConnected, STORE_E_OPERATION_CANCELED);
  2221. _Disconnect();
  2222. // The m_hrOperationResult and m_szOperationDetails/m_szOperationProblem
  2223. // vars can be blown away by by _OnCmdComplete caused by disconnect
  2224. m_hrOperationResult = STORE_E_OPERATION_CANCELED;
  2225. // Verify that we are indeed terminating current operation: if not, FORCE IT!
  2226. if (FALSE == m_fTerminating)
  2227. {
  2228. HRESULT hrTemp;
  2229. IxpAssert(FALSE); // This should not happen: fix the problem
  2230. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_ERROR);
  2231. TraceError(hrTemp);
  2232. }
  2233. return S_OK;
  2234. }
  2235. //---------------------------------------------------------------------------
  2236. //---------------------------------------------------------------------------
  2237. //***************************************************************************
  2238. //***************************************************************************
  2239. HRESULT CIMAPSync::_LoadAccountInfo()
  2240. {
  2241. HRESULT hr;
  2242. FOLDERINFO fi;
  2243. FOLDERINFO *pfiFree=NULL;
  2244. IImnAccount *pAcct=NULL;
  2245. CHAR szAccountId[CCHMAX_ACCOUNT_NAME];
  2246. TraceCall("CIMAPSync::_LoadAccountInfo");
  2247. IxpAssert (m_idIMAPServer);
  2248. IxpAssert (m_pStore);
  2249. IxpAssert (g_pAcctMan);
  2250. if (!m_pStore || !g_pAcctMan)
  2251. {
  2252. hr = E_UNEXPECTED;
  2253. goto exit;
  2254. }
  2255. hr = m_pStore->GetFolderInfo(m_idIMAPServer, &fi);
  2256. if (FAILED(hr))
  2257. {
  2258. TraceResult(hr);
  2259. goto exit;
  2260. }
  2261. pfiFree = &fi;
  2262. hr = GetFolderAccountId(&fi, szAccountId, ARRAYSIZE(szAccountId));
  2263. if (FAILED(hr))
  2264. {
  2265. TraceResult(hr);
  2266. goto exit;
  2267. }
  2268. m_pszAccountID = PszDupA(szAccountId);
  2269. if (!m_pszAccountID)
  2270. {
  2271. hr = TraceResult(E_OUTOFMEMORY);
  2272. goto exit;
  2273. }
  2274. hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, szAccountId, &pAcct);
  2275. if (FAILED(hr))
  2276. {
  2277. TraceResult(hr);
  2278. goto exit;
  2279. }
  2280. // failure of the account name is recoverable
  2281. pAcct->GetPropSz(AP_ACCOUNT_NAME, m_szAccountName, ARRAYSIZE(m_szAccountName));
  2282. exit:
  2283. if (pfiFree)
  2284. m_pStore->FreeRecord(pfiFree);
  2285. ReleaseObj(pAcct);
  2286. return hr;
  2287. }
  2288. HRESULT CIMAPSync::_LoadTransport()
  2289. {
  2290. HRESULT hr;
  2291. TCHAR szLogfilePath[MAX_PATH];
  2292. TCHAR *pszLogfilePath = NULL;
  2293. IImnAccount *pAcct=NULL;
  2294. TraceCall("CIMAPSync::_LoadTransport");
  2295. IxpAssert (g_pAcctMan);
  2296. IxpAssert (m_pszAccountID);
  2297. if (!g_pAcctMan)
  2298. {
  2299. hr = E_UNEXPECTED;
  2300. goto exit;
  2301. }
  2302. // Create and initialize IMAP transport
  2303. hr = CreateIMAPTransport2(&m_pTransport);
  2304. if (FAILED(hr))
  2305. {
  2306. TraceResult(hr);
  2307. goto exit;
  2308. }
  2309. // Check if logging is enabled
  2310. if (DwGetOption(OPT_MAIL_LOGIMAP4))
  2311. {
  2312. char szDirectory[MAX_PATH];
  2313. char szLogFileName[MAX_PATH];
  2314. DWORD cb;
  2315. *szDirectory = 0;
  2316. // Get the log filename
  2317. cb = GetOption(OPT_MAIL_IMAP4LOGFILE, szLogFileName, ARRAYSIZE(szLogFileName));
  2318. if (0 == cb)
  2319. {
  2320. // Bring out the defaults, and blast it back into registry
  2321. StrCpyN(szLogFileName, c_szDefaultImap4Log, ARRAYSIZE(szLogFileName));
  2322. SetOption(OPT_MAIL_IMAP4LOGFILE, (void *)c_szDefaultImap4Log,
  2323. lstrlen(c_szDefaultImap4Log) + sizeof(TCHAR), NULL, 0);
  2324. }
  2325. m_pStore->GetDirectory(szDirectory, ARRAYSIZE(szDirectory));
  2326. pszLogfilePath = PathCombineA(szLogfilePath, szDirectory, szLogFileName);
  2327. }
  2328. hr = m_pTransport->InitNew(pszLogfilePath, this);
  2329. if (FAILED(hr))
  2330. {
  2331. TraceResult(hr);
  2332. goto exit;
  2333. }
  2334. hr = m_pTransport->SetDefaultCP(IMAP_MBOXXLATE_DEFAULT | IMAP_MBOXXLATE_VERBATIMOK, GetACP());
  2335. TraceError(hr);
  2336. hr = m_pTransport->EnableFetchEx(IMAP_FETCHEX_ENABLE);
  2337. if (FAILED(hr))
  2338. {
  2339. // It would be easy for us to add code to handle irtUPDATE_MSG, but nothing is currently in place
  2340. TraceResult(hr);
  2341. goto exit;
  2342. }
  2343. hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_pszAccountID, &pAcct);
  2344. if (FAILED(hr))
  2345. {
  2346. TraceResult(hr);
  2347. goto exit;
  2348. }
  2349. // Fill m_rInetServerInfo
  2350. hr = m_pTransport->InetServerFromAccount(pAcct, &m_rInetServerInfo);
  2351. if (FAILED(hr))
  2352. {
  2353. TraceResult(hr);
  2354. goto exit;
  2355. }
  2356. exit:
  2357. ReleaseObj(pAcct);
  2358. return hr;
  2359. }
  2360. //***************************************************************************
  2361. //***************************************************************************
  2362. HRESULT CIMAPSync::_Connect(void)
  2363. {
  2364. HRESULT hr;
  2365. IXPSTATUS ixpCurrentStatus;
  2366. INETSERVER rServerInfo;
  2367. BOOL fForceReconnect = FALSE;
  2368. IImnAccount *pAcct;
  2369. HRESULT hrTemp;
  2370. TraceCall("CIMAPSync::_Connect");
  2371. IxpAssert (g_pAcctMan);
  2372. IxpAssert (m_cRef > 0);
  2373. IxpAssert (m_pTransport);
  2374. if (!g_pAcctMan)
  2375. return E_UNEXPECTED;
  2376. // Check if any connection settings changed
  2377. hrTemp = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_pszAccountID, &pAcct);
  2378. TraceError(hrTemp);
  2379. if (SUCCEEDED(hrTemp))
  2380. {
  2381. hrTemp = m_pTransport->InetServerFromAccount(pAcct, &rServerInfo);
  2382. TraceError(hrTemp);
  2383. if (SUCCEEDED(hrTemp))
  2384. {
  2385. // Check if anything changed
  2386. if (m_rInetServerInfo.rasconntype != rServerInfo.rasconntype ||
  2387. m_rInetServerInfo.dwPort != rServerInfo.dwPort ||
  2388. m_rInetServerInfo.fSSL != rServerInfo.fSSL ||
  2389. m_rInetServerInfo.fTrySicily != rServerInfo.fTrySicily ||
  2390. m_rInetServerInfo.dwTimeout != rServerInfo.dwTimeout ||
  2391. 0 != lstrcmp(m_rInetServerInfo.szUserName, rServerInfo.szUserName) ||
  2392. ('\0' != rServerInfo.szPassword[0] &&
  2393. 0 != lstrcmp(m_rInetServerInfo.szPassword, rServerInfo.szPassword)) ||
  2394. 0 != lstrcmp(m_rInetServerInfo.szServerName, rServerInfo.szServerName) ||
  2395. 0 != lstrcmp(m_rInetServerInfo.szConnectoid, rServerInfo.szConnectoid))
  2396. {
  2397. CopyMemory(&m_rInetServerInfo, &rServerInfo, sizeof(m_rInetServerInfo));
  2398. fForceReconnect = TRUE;
  2399. }
  2400. }
  2401. pAcct->Release();
  2402. }
  2403. // Find out if we're already connected or in the middle of connecting
  2404. hr = m_pTransport->GetStatus(&ixpCurrentStatus);
  2405. if (FAILED(hr))
  2406. {
  2407. // We'll call IIMAPTransport::Connect and see what happens
  2408. TraceResult(hr);
  2409. hr = S_OK; // Suppress error
  2410. ixpCurrentStatus = IXP_DISCONNECTED;
  2411. }
  2412. // If we're to force a reconnect and we're not currently disconnected, disconnect us
  2413. if (fForceReconnect && IXP_DISCONNECTED != ixpCurrentStatus)
  2414. {
  2415. m_fReconnect = TRUE; // Prohibit abortion of current operation due to disconnect
  2416. hrTemp = m_pTransport->DropConnection();
  2417. TraceError(hrTemp);
  2418. m_fReconnect = FALSE;
  2419. }
  2420. // Ask our client if we can connect. If no CB or if failure, we just try to connect
  2421. // Make sure we call CanConnect after DropConnection above, to avoid msg pumping
  2422. if (NULL != m_pCurrentCB)
  2423. {
  2424. hr = m_pCurrentCB->CanConnect(m_pszAccountID,
  2425. SOT_PURGING_MESSAGES == m_sotCurrent ? CC_FLAG_DONTPROMPT : NOFLAGS);
  2426. if (S_OK != hr)
  2427. {
  2428. // Make sure all non-S_OK success codes are treated as failures
  2429. // Convert all error codes to HR_E_USER_CANCEL_CONNECT if we were purging-on-exit
  2430. // This prevents error dialogs while purging-on-exit.
  2431. hr = TraceResult(hr);
  2432. if (SUCCEEDED(hr) || SOT_PURGING_MESSAGES == m_sotCurrent)
  2433. hr = HR_E_USER_CANCEL_CONNECT;
  2434. goto exit;
  2435. }
  2436. }
  2437. // If we're already in the middle of connecting, do nothing and return successful HRESULT
  2438. if (IXP_DISCONNECTED == ixpCurrentStatus || IXP_DISCONNECTING == ixpCurrentStatus ||
  2439. fForceReconnect)
  2440. {
  2441. // Make sure m_rInetServerInfo is loaded with latest cached password from user
  2442. // This allows reconnect without user intervention if user didn't save password
  2443. GetPassword(m_rInetServerInfo.dwPort, m_rInetServerInfo.szServerName,
  2444. m_rInetServerInfo.szUserName, m_rInetServerInfo.szPassword,
  2445. ARRAYSIZE(m_rInetServerInfo.szPassword));
  2446. // We're neither connected nor in the process of connecting: start connecting
  2447. hr = m_pTransport->Connect(&m_rInetServerInfo, iitAUTHENTICATE, iitDISABLE_ONCOMMAND);
  2448. }
  2449. else
  2450. {
  2451. // "Do Nothing" in the comment above now means to kick the FSM to the next state
  2452. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_CONNCOMPLETE);
  2453. TraceError(hrTemp);
  2454. }
  2455. exit:
  2456. return hr;
  2457. }
  2458. //***************************************************************************
  2459. //***************************************************************************
  2460. HRESULT CIMAPSync::_Disconnect(void)
  2461. {
  2462. HRESULT hr;
  2463. IXPSTATUS ixpCurrentStatus;
  2464. TraceCall("CIMAPSync::_Disconnect");
  2465. IxpAssert(m_cRef > 0);
  2466. // Find out if we're already disconnected or in the middle of disconnecting
  2467. hr = m_pTransport->GetStatus(&ixpCurrentStatus);
  2468. if (FAILED(hr))
  2469. {
  2470. // We'll call IIMAPTransport::DropConnection and see what happens
  2471. TraceResult(hr);
  2472. hr = S_OK; // Suppress error
  2473. ixpCurrentStatus = IXP_CONNECTED;
  2474. }
  2475. // If we're already in the middle of disconnecting, do nothing and return successful HRESULT
  2476. if (IXP_DISCONNECTED != ixpCurrentStatus &&
  2477. IXP_DISCONNECTING != ixpCurrentStatus && NULL != m_pTransport && FALSE == m_fDisconnecting)
  2478. {
  2479. m_fDisconnecting = TRUE;
  2480. m_hrOperationResult = STORE_E_OPERATION_CANCELED;
  2481. hr = m_pTransport->DropConnection();
  2482. }
  2483. return hr;
  2484. }
  2485. //***************************************************************************
  2486. //***************************************************************************
  2487. HRESULT CIMAPSync::_BeginOperation(STOREOPERATIONTYPE sotOpType,
  2488. IStoreCallback *pCallback)
  2489. {
  2490. HRESULT hr;
  2491. STOREOPERATIONINFO soi;
  2492. STOREOPERATIONINFO *psoi=NULL;
  2493. IxpAssert(SOT_INVALID != sotOpType);
  2494. m_sotCurrent = sotOpType;
  2495. ReplaceInterface(m_pCurrentCB, pCallback);
  2496. m_hrOperationResult = OLE_E_BLANK; // Unitialized state
  2497. m_szOperationProblem[0] = '\0';
  2498. m_szOperationDetails[0] = '\0';
  2499. m_fTerminating = FALSE;
  2500. // Kickstart the connection state machine
  2501. hr = _ConnFSM_QueueEvent(CFSM_EVENT_CMDAVAIL);
  2502. if (FAILED(hr))
  2503. {
  2504. TraceResult(hr);
  2505. }
  2506. else
  2507. {
  2508. if (sotOpType == SOT_GET_MESSAGE)
  2509. {
  2510. // provide message id on get message start
  2511. soi.cbSize = sizeof(STOREOPERATIONINFO);
  2512. soi.idMessage = m_idMessage;
  2513. psoi = &soi;
  2514. }
  2515. if (pCallback)
  2516. pCallback->OnBegin(sotOpType, psoi, this);
  2517. hr = E_PENDING;
  2518. }
  2519. return hr;
  2520. }
  2521. //***************************************************************************
  2522. // Function: _EnqueueOperation
  2523. //
  2524. // Purpose:
  2525. // This function enqueues IMAP operations for execution once we have
  2526. // entered the SELECTED state on the IMAP server.
  2527. //
  2528. // Arguments:
  2529. // WPARAM wParam [in] - transaction ID identifying this operation.
  2530. // This ID is always returned to CmdCompletionNotification, and possibly
  2531. // returned to any untagged responses which result from the given cmd.
  2532. // LPARAM lParam [in] - lParam associated with this transaction.
  2533. // IMAP_COMMAND icCommandID [in] - this identifies the IMAP command the
  2534. // caller wishes to send to the IMAP server.
  2535. // LPSTR pszCmdArgs [in] - the command arguments. Pass in NULL if the
  2536. // queued command has no arguments.
  2537. // UINT uiPriority [in] - a priority associated with this IMAP command.
  2538. // The value of "0" is highest priority. Before an IMAP command of
  2539. // a given priority can be sent, there must be NO higher-priority cmds
  2540. // waiting.
  2541. //
  2542. // Returns:
  2543. // HRESULT indicating success or failure.
  2544. //***************************************************************************
  2545. HRESULT CIMAPSync::_EnqueueOperation(WPARAM wParam, LPARAM lParam,
  2546. IMAP_COMMAND icCommandID, LPCSTR pszCmdArgs,
  2547. UINT uiPriority)
  2548. {
  2549. IMAP_OPERATION *pioCommand;
  2550. IMAP_OPERATION *pioPrev, *pioCurrent;
  2551. HRESULT hr = S_OK;
  2552. TraceCall("CIMAPSync::_EnqueueOperation");
  2553. IxpAssert(m_cRef > 0);
  2554. // Construct a IMAP_OPERATION queue element for this command
  2555. pioCommand = new IMAP_OPERATION;
  2556. if (NULL == pioCommand)
  2557. {
  2558. hr = TraceResult(E_OUTOFMEMORY);
  2559. goto exit;
  2560. }
  2561. pioCommand->wParam = wParam;
  2562. pioCommand->lParam = lParam;
  2563. pioCommand->icCommandID = icCommandID;
  2564. pioCommand->pszCmdArgs = PszDupA(pszCmdArgs);
  2565. pioCommand->uiPriority = uiPriority;
  2566. pioCommand->issMinimum = IMAPCmdToMinISS(icCommandID);
  2567. IxpAssert(pioCommand->issMinimum >= issNonAuthenticated);
  2568. // Refcount if this is a streamed operation
  2569. if (tidRENAME == wParam ||
  2570. tidRENAMESUBSCRIBE == wParam ||
  2571. tidRENAMELIST == wParam ||
  2572. tidRENAMERENAME == wParam ||
  2573. tidRENAMESUBSCRIBE_AGAIN == wParam ||
  2574. tidRENAMEUNSUBSCRIBE == wParam)
  2575. ((CRenameFolderInfo *)lParam)->AddRef();
  2576. // Insert element into the queue
  2577. // Find a node which has lower priority than the cmd we want to enqueue
  2578. pioPrev = NULL;
  2579. pioCurrent = m_pioNextOperation;
  2580. while (NULL != pioCurrent && pioCurrent->uiPriority <= uiPriority)
  2581. {
  2582. // Advance both pointers
  2583. pioPrev = pioCurrent;
  2584. pioCurrent = pioCurrent->pioNextCommand;
  2585. }
  2586. // pioPrev now points to the insertion point
  2587. if (NULL == pioPrev)
  2588. {
  2589. // Insert command at head of queue
  2590. pioCommand->pioNextCommand = m_pioNextOperation;
  2591. m_pioNextOperation = pioCommand;
  2592. }
  2593. else {
  2594. // Insert command in middle/end of queue
  2595. pioCommand->pioNextCommand = pioCurrent;
  2596. pioPrev->pioNextCommand = pioCommand;
  2597. }
  2598. // Try to send immediately if we're in correct state
  2599. if (CFSM_STATE_WAITFOROPERATIONDONE == m_cfsState)
  2600. {
  2601. do {
  2602. hr = _SendNextOperation(snoDO_NOT_DISPOSE);
  2603. TraceError(hr);
  2604. } while (S_OK == hr);
  2605. }
  2606. exit:
  2607. return hr;
  2608. }
  2609. //***************************************************************************
  2610. // Function: _SendNextOperation
  2611. //
  2612. // Purpose:
  2613. // This function sends the next IMAP operation in the queue if the
  2614. // conditions are correct. Currently, these conditions are:
  2615. // a) We are in the SELECTED state on the IMAP server
  2616. // b) The IMAP operation queue is not empty
  2617. //
  2618. // Arguments:
  2619. // DWORD dwFlags [in] - one of the following:
  2620. // snoDO_NOT_DISPOSE - do not dispose of LPARAM if error occurs, typically
  2621. // because EnqueueOperation will return error to caller thus causing
  2622. // caller to dispose of data.
  2623. //
  2624. // Returns:
  2625. // S_OK if there are more operations available to be sent. S_FALSE if no more
  2626. // IMAP operations can be sent at this time. Failure result if an error occured.
  2627. //***************************************************************************
  2628. HRESULT CIMAPSync::_SendNextOperation(DWORD dwFlags)
  2629. {
  2630. IMAP_OPERATION *pioNextCmd;
  2631. IMAP_OPERATION *pioPrev;
  2632. HRESULT hr;
  2633. TraceCall("CIMAPSync::_SendNextOperation");
  2634. IxpAssert(m_cRef > 0);
  2635. // Check if conditions permit sending of an IMAP operation
  2636. hr = m_pTransport->IsState(IXP_IS_AUTHENTICATED);
  2637. if (S_OK != hr)
  2638. {
  2639. hr = S_FALSE; // No operations to send (YET)
  2640. goto exit;
  2641. }
  2642. // Look for next eligible command
  2643. hr = GetNextOperation(&pioNextCmd);
  2644. if (STORE_S_NOOP == hr || FAILED(hr))
  2645. {
  2646. TraceError(hr);
  2647. hr = S_FALSE;
  2648. goto exit;
  2649. }
  2650. // Send it
  2651. hr = S_OK;
  2652. switch (pioNextCmd->icCommandID)
  2653. {
  2654. case icFETCH_COMMAND:
  2655. {
  2656. LPSTR pszFetchArgs;
  2657. char szFetchArgs[CCHMAX_CMDLINE];
  2658. // Check if this is a BODY FETCH. We have to construct args for body fetch
  2659. if (tidBODY == pioNextCmd->wParam)
  2660. {
  2661. DWORD dwCapabilities;
  2662. // Check if this is IMAP4 or IMAP4rev1 (RFC822.PEEK or BODY.PEEK[])
  2663. IxpAssert(NULL == pioNextCmd->pszCmdArgs);
  2664. hr = m_pTransport->Capability(&dwCapabilities);
  2665. if (FAILED(hr))
  2666. {
  2667. TraceResult(hr);
  2668. dwCapabilities = IMAP_CAPABILITY_IMAP4; // Carry on assuming IMAP4
  2669. }
  2670. wnsprintf(szFetchArgs, ARRAYSIZE(szFetchArgs), "%lu (%s UID)", pioNextCmd->lParam,
  2671. (dwCapabilities & IMAP_CAPABILITY_IMAP4rev1) ? "BODY.PEEK[]" : "RFC822.PEEK");
  2672. pszFetchArgs = szFetchArgs;
  2673. }
  2674. else
  2675. {
  2676. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2677. pszFetchArgs = pioNextCmd->pszCmdArgs;
  2678. }
  2679. hr = m_pTransport->Fetch(pioNextCmd->wParam, pioNextCmd->lParam,
  2680. this, NULL, USE_UIDS, pszFetchArgs); // We always use UIDs
  2681. TraceError(hr);
  2682. }
  2683. break;
  2684. case icSTORE_COMMAND:
  2685. {
  2686. MARK_MSGS_INFO *pMARK_MSGS_INFO;
  2687. pMARK_MSGS_INFO = (MARK_MSGS_INFO *) pioNextCmd->lParam;
  2688. IxpAssert(tidMARKMSGS == pioNextCmd->wParam);
  2689. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2690. IxpAssert(NULL != pioNextCmd->lParam);
  2691. hr = m_pTransport->Store(pioNextCmd->wParam,
  2692. pioNextCmd->lParam, this, pMARK_MSGS_INFO->pMsgRange,
  2693. USE_UIDS, pioNextCmd->pszCmdArgs); // We always use UIDs
  2694. TraceError(hr);
  2695. }
  2696. break;
  2697. case icCOPY_COMMAND:
  2698. {
  2699. IMAP_COPYMOVE_INFO *pCopyMoveInfo;
  2700. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2701. IxpAssert(NULL != pioNextCmd->lParam);
  2702. pCopyMoveInfo = (IMAP_COPYMOVE_INFO *) pioNextCmd->lParam;
  2703. IxpAssert(NULL != pCopyMoveInfo->pCopyRange);
  2704. // Find out what translation mode we should be in
  2705. hr = SetTranslationMode(pCopyMoveInfo->idDestFldr);
  2706. if (FAILED(hr))
  2707. {
  2708. TraceResult(hr);
  2709. break;
  2710. }
  2711. hr = m_pTransport->Copy(pioNextCmd->wParam, pioNextCmd->lParam,
  2712. this, pCopyMoveInfo->pCopyRange,
  2713. USE_UIDS, pioNextCmd->pszCmdArgs); // We always use UIDs
  2714. TraceError(hr);
  2715. }
  2716. break; // icCOPY_COMMAND
  2717. case icCLOSE_COMMAND:
  2718. IxpAssert(NULL == pioNextCmd->pszCmdArgs);
  2719. hr = m_pTransport->Close(pioNextCmd->wParam, pioNextCmd->lParam, this);
  2720. TraceError(hr);
  2721. break;
  2722. case icAPPEND_COMMAND:
  2723. {
  2724. APPEND_SEND_INFO *pAppendInfo;
  2725. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2726. IxpAssert(NULL != pioNextCmd->lParam);
  2727. pAppendInfo = (APPEND_SEND_INFO *) pioNextCmd->lParam;
  2728. hr = m_pTransport->Append(pioNextCmd->wParam, pioNextCmd->lParam,
  2729. this, pioNextCmd->pszCmdArgs, pAppendInfo->pszMsgFlags,
  2730. pAppendInfo->ftReceived, pAppendInfo->lpstmMsg);
  2731. TraceError(hr);
  2732. }
  2733. break; // case icAPPEND_COMMAND
  2734. case icLIST_COMMAND:
  2735. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2736. hr = m_pTransport->List(pioNextCmd->wParam, pioNextCmd->lParam,
  2737. this, "", pioNextCmd->pszCmdArgs); // Reference is always blank
  2738. TraceError(hr);
  2739. break; // case icLIST_COMMAND
  2740. case icLSUB_COMMAND:
  2741. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2742. hr = m_pTransport->Lsub(pioNextCmd->wParam, pioNextCmd->lParam,
  2743. this, "", pioNextCmd->pszCmdArgs); // Reference is always blank
  2744. TraceError(hr);
  2745. break; // case icLSUB_COMMAND
  2746. case icCREATE_COMMAND:
  2747. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2748. hr = m_pTransport->Create(pioNextCmd->wParam, pioNextCmd->lParam,
  2749. this, pioNextCmd->pszCmdArgs);
  2750. TraceError(hr);
  2751. break; // case icCREATE_COMMAND
  2752. case icSUBSCRIBE_COMMAND:
  2753. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2754. hr = m_pTransport->Subscribe(pioNextCmd->wParam, pioNextCmd->lParam,
  2755. this, pioNextCmd->pszCmdArgs);
  2756. TraceError(hr);
  2757. break; // case icSUBSCRIBE_COMMAND
  2758. case icDELETE_COMMAND:
  2759. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2760. hr = m_pTransport->Delete(pioNextCmd->wParam, pioNextCmd->lParam,
  2761. this, pioNextCmd->pszCmdArgs);
  2762. TraceError(hr);
  2763. break; // case icDELETE_COMMAND
  2764. case icUNSUBSCRIBE_COMMAND:
  2765. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2766. hr = m_pTransport->Unsubscribe(pioNextCmd->wParam, pioNextCmd->lParam,
  2767. this, pioNextCmd->pszCmdArgs);
  2768. TraceError(hr);
  2769. break; // case icUNSUBSCRIBE_COMMAND
  2770. case icRENAME_COMMAND:
  2771. {
  2772. CRenameFolderInfo *pRenameInfo;
  2773. LPSTR pszOldFldrName;
  2774. IxpAssert(NULL != pioNextCmd->pszCmdArgs);
  2775. IxpAssert(NULL != pioNextCmd->lParam);
  2776. pRenameInfo = (CRenameFolderInfo *) pioNextCmd->lParam;
  2777. hr = m_pTransport->Rename(pioNextCmd->wParam, (LPARAM) pRenameInfo,
  2778. this, pRenameInfo->pszRenameCmdOldFldrPath, pioNextCmd->pszCmdArgs);
  2779. TraceError(hr);
  2780. } // case icRENAME_COMMAND
  2781. break; // case icRENAME_COMMAND
  2782. case icSTATUS_COMMAND:
  2783. {
  2784. DWORD dwCapabilities;
  2785. IxpAssert(FOLDERID_INVALID != (FOLDERID)pioNextCmd->lParam);
  2786. // Have to check if this is an IMAP4rev1 server. If not, FAIL the Status operation
  2787. hr = m_pTransport->Capability(&dwCapabilities);
  2788. if (SUCCEEDED(hr) && 0 == (dwCapabilities & IMAP_CAPABILITY_IMAP4rev1))
  2789. {
  2790. // Can't currently check unread count for non-IMAP4rev1 servers
  2791. hr = STORE_E_NOSERVERSUPPORT;
  2792. }
  2793. else
  2794. {
  2795. hr = m_pTransport->Status(pioNextCmd->wParam, pioNextCmd->lParam,
  2796. this, pioNextCmd->pszCmdArgs, "(MESSAGES UNSEEN)");
  2797. }
  2798. }
  2799. break;
  2800. default:
  2801. AssertSz(FALSE, "Someone queued an operation I can't handle!");
  2802. break;
  2803. }
  2804. // Handle any errors encountered above
  2805. if (FAILED(hr))
  2806. {
  2807. TraceResult(hr);
  2808. // Free any non-NULL lParam's and call IStoreCallback::OnComplete
  2809. if (0 == (dwFlags & snoDO_NOT_DISPOSE))
  2810. {
  2811. if ('\0' == m_szOperationDetails)
  2812. // Fill in error information: error propagation causes IStoreCallback::OnComplete call
  2813. LoadString(g_hLocRes, idsIMAPSendNextOpErrText, m_szOperationDetails, ARRAYSIZE(m_szOperationDetails));
  2814. DisposeOfWParamLParam(pioNextCmd->wParam, pioNextCmd->lParam, hr);
  2815. }
  2816. }
  2817. // Deallocate the imap operation
  2818. if (NULL != pioNextCmd->pszCmdArgs)
  2819. MemFree(pioNextCmd->pszCmdArgs);
  2820. delete pioNextCmd;
  2821. exit:
  2822. return hr;
  2823. }
  2824. //***************************************************************************
  2825. // Function: FlushOperationQueue
  2826. //
  2827. // Purpose:
  2828. // This function frees the entire contents of the IMAP operation queue.
  2829. // Usually used by the CIMAPSync destructor, and whenever an error occurs
  2830. // which would prevent the sending of queued IMAP operations (eg, login
  2831. // failure).
  2832. //
  2833. // Arguments:
  2834. // IMAP_SERVERSTATE issMaximum [in] - defines the maximum server state
  2835. // currently allowed in the queue. For instance, if a select failed,
  2836. // we would call FlushOperationQueue(issAuthenticated) to remove all
  2837. // commands that require issSelected as their minimum state. To remove
  2838. // all commands, pass in issNotConnected.
  2839. //***************************************************************************
  2840. void CIMAPSync::FlushOperationQueue(IMAP_SERVERSTATE issMaximum, HRESULT hrError)
  2841. {
  2842. IMAP_OPERATION *pioCurrent;
  2843. IMAP_OPERATION *pioPrev;
  2844. IxpAssert(((int) m_cRef) >= 0); // Can be called by destructor
  2845. pioPrev = NULL;
  2846. pioCurrent = m_pioNextOperation;
  2847. while (NULL != pioCurrent)
  2848. {
  2849. // Check if current command should be deleted
  2850. if (pioCurrent->issMinimum > issMaximum)
  2851. {
  2852. IMAP_OPERATION *pioDead;
  2853. HRESULT hr;
  2854. // Current command level exceeds the maximum. Unlink from queue and delete
  2855. pioDead = pioCurrent;
  2856. if (NULL == pioPrev)
  2857. {
  2858. // Dequeue from head of queue
  2859. m_pioNextOperation = pioCurrent->pioNextCommand;
  2860. pioCurrent = pioCurrent->pioNextCommand;
  2861. }
  2862. else
  2863. {
  2864. // Dequeue from mid/end of queue
  2865. pioPrev->pioNextCommand = pioCurrent->pioNextCommand;
  2866. pioCurrent = pioCurrent->pioNextCommand;
  2867. }
  2868. // Free any non-NULL lParam's and call IStoreCallback::OnComplete
  2869. if ('\0' == m_szOperationDetails)
  2870. // Fill in error information: error propagation causes IStoreCallback::OnComplete call
  2871. LoadString(g_hLocRes, idsIMAPSendNextOpErrText, m_szOperationDetails,
  2872. ARRAYSIZE(m_szOperationDetails));
  2873. DisposeOfWParamLParam(pioDead->wParam, pioDead->lParam, hrError);
  2874. if (NULL != pioDead->pszCmdArgs)
  2875. MemFree(pioDead->pszCmdArgs);
  2876. delete pioDead;
  2877. }
  2878. else
  2879. {
  2880. // Current command is within maximum level. Advance pointers
  2881. pioPrev = pioCurrent;
  2882. pioCurrent = pioCurrent->pioNextCommand;
  2883. }
  2884. } // while
  2885. } // FlushOperationQueue
  2886. //***************************************************************************
  2887. //***************************************************************************
  2888. IMAP_SERVERSTATE CIMAPSync::IMAPCmdToMinISS(IMAP_COMMAND icCommandID)
  2889. {
  2890. IMAP_SERVERSTATE issResult;
  2891. TraceCall("CIMAPSync::IMAPCmdToMinISS");
  2892. switch (icCommandID)
  2893. {
  2894. case icSELECT_COMMAND:
  2895. case icEXAMINE_COMMAND:
  2896. case icCREATE_COMMAND:
  2897. case icDELETE_COMMAND:
  2898. case icRENAME_COMMAND:
  2899. case icSUBSCRIBE_COMMAND:
  2900. case icUNSUBSCRIBE_COMMAND:
  2901. case icLIST_COMMAND:
  2902. case icLSUB_COMMAND:
  2903. case icAPPEND_COMMAND:
  2904. case icSTATUS_COMMAND:
  2905. issResult = issAuthenticated;
  2906. break;
  2907. case icCLOSE_COMMAND:
  2908. case icSEARCH_COMMAND:
  2909. case icFETCH_COMMAND:
  2910. case icSTORE_COMMAND:
  2911. case icCOPY_COMMAND:
  2912. issResult = issSelected;
  2913. break;
  2914. default:
  2915. AssertSz(FALSE, "What command are you trying to send?");
  2916. // *** FALL THROUGH ***
  2917. case icLOGOUT_COMMAND:
  2918. case icNOOP_COMMAND:
  2919. issResult = issNonAuthenticated;
  2920. break;
  2921. }
  2922. return issResult;
  2923. }
  2924. //***************************************************************************
  2925. //***************************************************************************
  2926. HRESULT CIMAPSync::GetNextOperation(IMAP_OPERATION **ppioOp)
  2927. {
  2928. HRESULT hr = S_OK;
  2929. IMAP_OPERATION *pioCurrent;
  2930. IMAP_OPERATION *pioPrev;
  2931. TraceCall("CIMAPSync::GetNextOperation");
  2932. IxpAssert(m_cRef > 0);
  2933. IxpAssert(NULL != ppioOp);
  2934. pioPrev = NULL;
  2935. pioCurrent = m_pioNextOperation;
  2936. while (NULL != pioCurrent)
  2937. {
  2938. // Check if we are able to send the current command
  2939. if (pioCurrent->issMinimum <= m_issCurrent)
  2940. break;
  2941. // Advance pointers
  2942. pioPrev = pioCurrent;
  2943. pioCurrent = pioCurrent->pioNextCommand;
  2944. }
  2945. // Check if we found anything
  2946. if (NULL == pioCurrent)
  2947. {
  2948. hr = STORE_S_NOOP; // Nothing to send at the moment
  2949. goto exit;
  2950. }
  2951. // If we reached here, we found something. Dequeue operation.
  2952. *ppioOp = pioCurrent;
  2953. if (NULL == pioPrev)
  2954. {
  2955. // Dequeue from head of queue
  2956. m_pioNextOperation = pioCurrent->pioNextCommand;
  2957. }
  2958. else
  2959. {
  2960. // Dequeue from mid/end of queue
  2961. pioPrev->pioNextCommand = pioCurrent->pioNextCommand;
  2962. }
  2963. exit:
  2964. return hr;
  2965. }
  2966. //***************************************************************************
  2967. // Function: DisposeofWParamLParam
  2968. //
  2969. // Purpose:
  2970. // This function eliminates the wParam and lParam arguments of an IMAP
  2971. // operation in the event of failure.
  2972. //
  2973. // Arguments:
  2974. // WPARAM wParam - the wParam of the failed IMAP operation.
  2975. // LPARAM lPAram - the lParam of the failed IMAP operation.
  2976. // HRESULT hr - the error condition that caused the IMAP op failure
  2977. //***************************************************************************
  2978. void CIMAPSync::DisposeOfWParamLParam(WPARAM wParam, LPARAM lParam, HRESULT hr)
  2979. {
  2980. TraceCall("CIMAPSync::DisposeofWParamLParam");
  2981. IxpAssert(m_cRef > 0);
  2982. AssertSz(FAILED(hr), "If you didn't fail, why are you here?");
  2983. switch (wParam)
  2984. {
  2985. case tidCOPYMSGS:
  2986. case tidMOVEMSGS:
  2987. {
  2988. IMAP_COPYMOVE_INFO *pCopyMoveInfo;
  2989. // Notify the user that the operation has failed, and free the structure
  2990. pCopyMoveInfo = (IMAP_COPYMOVE_INFO *) lParam;
  2991. SafeMemFree(pCopyMoveInfo->pList);
  2992. pCopyMoveInfo->pCopyRange->Release();
  2993. delete pCopyMoveInfo;
  2994. break;
  2995. }
  2996. case tidBODYMSN:
  2997. case tidBODY:
  2998. LoadString(g_hLocRes, idsIMAPBodyFetchFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  2999. NotifyMsgRecipients(lParam, fCOMPLETED, NULL, hr, m_szOperationProblem);
  3000. break; // case tidBODYMSN, tidBODY
  3001. case tidMARKMSGS:
  3002. {
  3003. MARK_MSGS_INFO *pMarkMsgInfo;
  3004. // Notify the user that the operation has failed, and free the structure
  3005. pMarkMsgInfo = (MARK_MSGS_INFO *) lParam;
  3006. SafeMemFree(pMarkMsgInfo->pList);
  3007. SafeRelease(pMarkMsgInfo->pMsgRange);
  3008. delete pMarkMsgInfo;
  3009. break;
  3010. }
  3011. break; // case tidMARKMSGS
  3012. case tidFETCH_NEW_HDRS:
  3013. IxpAssert(lParam == NULL);
  3014. break;
  3015. case tidFETCH_CACHED_FLAGS:
  3016. IxpAssert(lParam == NULL);
  3017. break;
  3018. case tidNOOP:
  3019. IxpAssert(lParam == NULL);
  3020. break;
  3021. case tidSELECTION:
  3022. IxpAssert(lParam != NULL);
  3023. break;
  3024. case tidUPLOADMSG:
  3025. {
  3026. APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) lParam;
  3027. SafeMemFree(pAppendInfo->pszMsgFlags);
  3028. SafeRelease(pAppendInfo->lpstmMsg);
  3029. delete pAppendInfo;
  3030. }
  3031. break;
  3032. case tidCREATE:
  3033. case tidCREATELIST:
  3034. case tidCREATESUBSCRIBE:
  3035. case tidSPECIALFLDRLIST:
  3036. case tidSPECIALFLDRLSUB:
  3037. case tidSPECIALFLDRSUBSCRIBE:
  3038. {
  3039. CREATE_FOLDER_INFO *pcfiCreateInfo;
  3040. pcfiCreateInfo = (CREATE_FOLDER_INFO *) lParam;
  3041. MemFree(pcfiCreateInfo->pszFullFolderPath);
  3042. if (NULL != pcfiCreateInfo->lParam)
  3043. {
  3044. switch (pcfiCreateInfo->pcoNextOp)
  3045. {
  3046. case PCO_NONE:
  3047. AssertSz(FALSE, "Expected NULL lParam. Check for memleak.");
  3048. break;
  3049. case PCO_FOLDERLIST:
  3050. AssertSz(FOLDERID_INVALID == (FOLDERID) pcfiCreateInfo->lParam,
  3051. "Expected FOLDERID_INVALID lParam. Check for memleak.");
  3052. break;
  3053. case PCO_APPENDMSG:
  3054. {
  3055. APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) pcfiCreateInfo->lParam;
  3056. SafeMemFree(pAppendInfo->pszMsgFlags);
  3057. SafeRelease(pAppendInfo->lpstmMsg);
  3058. delete pAppendInfo;
  3059. }
  3060. break;
  3061. default:
  3062. AssertSz(FALSE, "Unhandled CREATE_FOLDER_INFO lParam. Check for memleak.");
  3063. break;
  3064. } // switch
  3065. }
  3066. delete pcfiCreateInfo;
  3067. break;
  3068. }
  3069. case tidDELETEFLDR:
  3070. case tidDELETEFLDR_UNSUBSCRIBE:
  3071. MemFree(((DELETE_FOLDER_INFO *)lParam)->pszFullFolderPath);
  3072. MemFree((DELETE_FOLDER_INFO *)lParam);
  3073. break; // case tidDELETEFLDR_UNSUBSCRIBE
  3074. case tidSUBSCRIBE:
  3075. IxpAssert(NULL == lParam);
  3076. break;
  3077. case tidRENAME:
  3078. case tidRENAMESUBSCRIBE:
  3079. case tidRENAMELIST:
  3080. case tidRENAMERENAME:
  3081. case tidRENAMESUBSCRIBE_AGAIN:
  3082. case tidRENAMEUNSUBSCRIBE:
  3083. ((CRenameFolderInfo *) lParam)->Release();
  3084. break;
  3085. case tidHIERARCHYCHAR_LIST_B:
  3086. case tidHIERARCHYCHAR_CREATE:
  3087. case tidHIERARCHYCHAR_LIST_C:
  3088. case tidHIERARCHYCHAR_DELETE:
  3089. case tidPREFIXLIST:
  3090. case tidPREFIX_HC:
  3091. case tidPREFIX_CREATE:
  3092. case tidFOLDERLIST:
  3093. case tidFOLDERLSUB:
  3094. case tidSTATUS:
  3095. break;
  3096. default:
  3097. AssertSz(NULL == lParam, "Is this a possible memory leak?");
  3098. break;
  3099. }
  3100. } // DisposeOfWParamLParam
  3101. //***************************************************************************
  3102. // Function: NotifyMsgRecipients
  3103. //
  3104. // Purpose:
  3105. // This function sends notifications to all registered recipients of a
  3106. // given message UID. Currently handles IMC_BODYAVAIL and IMC_ARTICLEPROG
  3107. // messages.
  3108. //
  3109. // Arguments:
  3110. // DWORD dwUID [in] - the UID identifying the message whose recipients
  3111. // are to be updated.
  3112. // BOOL fCompletion [in] - TRUE if we're done fetching the msg body.
  3113. // FALSE if we're still in the middle of fetching (progress indication)
  3114. // FETCH_BODY_PART *pFBPart [in] - fragment of FETCH body currently being
  3115. // downloaded. Should always be NULL if fCompletion is TRUE.
  3116. // HRESULT hrCompletion [in] - completion result. Should always be S_OK
  3117. // if fCompletion is FALSE.
  3118. // LPSTR pszDetails [in] - error message details for completion. Should
  3119. // always be NULL unless fCompletion is TRUE and hrCompletion is a
  3120. // failure code.
  3121. //***************************************************************************
  3122. void CIMAPSync::NotifyMsgRecipients(DWORD_PTR dwUID, BOOL fCompletion,
  3123. FETCH_BODY_PART *pFBPart,
  3124. HRESULT hrCompletion, LPSTR pszDetails)
  3125. {
  3126. HRESULT hrTemp; // For recording non-fatal errors
  3127. ADJUSTFLAGS flags;
  3128. MESSAGEIDLIST list;
  3129. TraceCall("CIMAPSync::NotifyMsgRecipients");
  3130. IxpAssert(m_cRef > 0);
  3131. IxpAssert(0 != dwUID);
  3132. AssertSz(NULL == pFBPart || FALSE == fCompletion, "pFBPart must be NULL if fCompletion TRUE!");
  3133. AssertSz(NULL != pFBPart || fCompletion, "pFBPart cannot be NULL if fCompletion FALSE!");
  3134. AssertSz(NULL == pszDetails || fCompletion, "pszDetails must be NULL if fCompletion FALSE!");
  3135. AssertSz(S_OK == hrCompletion || fCompletion, "hrCompletion must be S_OK if fCompletion FALSE!");
  3136. IxpAssert(m_pCurrentCB || fCompletion);
  3137. IxpAssert(m_pstmBody);
  3138. IxpAssert(m_idMessage || FALSE == fCompletion);
  3139. // If this is a failed completion, fill out a STOREERROR struct
  3140. if (fCompletion && FAILED(hrCompletion))
  3141. {
  3142. if (IS_INTRESOURCE(pszDetails))
  3143. {
  3144. // pszDetails is actually a string resource, so load it up
  3145. LoadString(g_hLocRes, PtrToUlong(pszDetails), m_szOperationDetails,
  3146. ARRAYSIZE(m_szOperationDetails));
  3147. pszDetails = m_szOperationDetails;
  3148. }
  3149. LoadString(g_hLocRes, idsIMAPDnldDlgDLFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  3150. }
  3151. if (fCompletion)
  3152. {
  3153. if (SUCCEEDED(hrCompletion))
  3154. {
  3155. HRESULT hr;
  3156. hr = CommitMessageToStore(m_pFolder, NULL, m_idMessage, m_pstmBody);
  3157. if (FAILED(hr))
  3158. {
  3159. TraceResult(hr);
  3160. LoadString(g_hLocRes, idsErrSetMessageStreamFailed, m_szOperationProblem,
  3161. ARRAYSIZE(m_szOperationProblem));
  3162. }
  3163. else
  3164. m_faStream = 0;
  3165. }
  3166. else if (hrCompletion == STORE_E_EXPIRED)
  3167. {
  3168. list.cAllocated = 0;
  3169. list.cMsgs = 1;
  3170. list.prgidMsg = &m_idMessage;
  3171. flags.dwAdd = ARF_ARTICLE_EXPIRED;
  3172. flags.dwRemove = ARF_DOWNLOAD;
  3173. Assert(m_pFolder);
  3174. m_pFolder->SetMessageFlags(&list, &flags, NULL, NULL);
  3175. //m_pFolder->SetMessageStream(m_idMessage, m_pstmBody);
  3176. }
  3177. SafeRelease(m_pstmBody);
  3178. if (0 != m_faStream)
  3179. {
  3180. Assert(m_pFolder);
  3181. m_pFolder->DeleteStream(m_faStream);
  3182. m_faStream = 0;
  3183. }
  3184. }
  3185. else
  3186. {
  3187. DWORD dwCurrent;
  3188. DWORD dwTotal;
  3189. ULONG ulWritten;
  3190. // Write this fragment to the stream
  3191. IxpAssert(fbpBODY == pFBPart->lpFetchCookie1);
  3192. hrTemp = m_pstmBody->Write(pFBPart->pszData, pFBPart->dwSizeOfData, &ulWritten);
  3193. if (FAILED(hrTemp))
  3194. m_hrOperationResult = TraceResult(hrTemp); // Make sure we don't commit stream
  3195. else
  3196. IxpAssert(ulWritten == pFBPart->dwSizeOfData);
  3197. if (pFBPart->dwSizeOfData > 0)
  3198. m_fGotBody = TRUE;
  3199. // Indicate message download progress
  3200. if (pFBPart->dwTotalBytes > 0)
  3201. {
  3202. dwCurrent = pFBPart->dwOffset + pFBPart->dwSizeOfData;
  3203. dwTotal = pFBPart->dwTotalBytes;
  3204. m_pCurrentCB->OnProgress(SOT_GET_MESSAGE, dwCurrent, dwTotal, NULL);
  3205. }
  3206. }
  3207. } // NotifyMsgRecipients
  3208. //***************************************************************************
  3209. // Function: OnFolderExit
  3210. //
  3211. // Purpose:
  3212. // This function is called when a folder is exited (currently occurs only
  3213. // through a disconnect). It resets the module's folder-specific variables
  3214. // so that re-connection to the folder (or a different folder) cause
  3215. // carry-over of information from the previous session.
  3216. //***************************************************************************
  3217. void CIMAPSync::OnFolderExit(void)
  3218. {
  3219. HRESULT hrTemp;
  3220. TraceCall("CIMAPSync::OnFolderExit");
  3221. IxpAssert(m_cRef > 0);
  3222. m_dwMsgCount = 0;
  3223. m_fMsgCountValid = FALSE;
  3224. m_dwNumHdrsDLed = 0;
  3225. m_dwNumUnreadDLed = 0;
  3226. m_dwNumHdrsToDL = 0;
  3227. m_dwUIDValidity = 0;
  3228. m_dwSyncToDo = 0; // Leave m_dwSyncFolderFlags as-is, so we can re-sync on re-connect
  3229. m_dwHighestCachedUID = 0;
  3230. m_rwsReadWriteStatus = rwsUNINITIALIZED;
  3231. m_fNewMail = FALSE;
  3232. m_fInbox = FALSE;
  3233. m_fDidFullSync = FALSE;
  3234. m_idSelectedFolder = FOLDERID_INVALID;
  3235. // Clear MsgSeqNumToUID table
  3236. hrTemp = m_pTransport->ResetMsgSeqNumToUID();
  3237. TraceError(hrTemp);
  3238. }
  3239. //***************************************************************************
  3240. //***************************************************************************
  3241. void CIMAPSync::FillStoreError(LPSTOREERROR pErrorInfo, HRESULT hr,
  3242. DWORD dwSocketError, LPSTR pszProblem,
  3243. LPSTR pszDetails)
  3244. {
  3245. DWORD dwFlags = 0;
  3246. TraceCall("CIMAPSync::FillStoreError");
  3247. IxpAssert(((int) m_cRef) >= 0); // Can be called during destruction
  3248. IxpAssert(NULL != pErrorInfo);
  3249. // pszProblem/pszDetails = NULL means m_szOperationProblem/m_szOperationDetails already filled out
  3250. // Use defaults if any of the text fields are blank
  3251. if (NULL != pszProblem && IS_INTRESOURCE(pszProblem))
  3252. {
  3253. LoadString(g_hLocRes, PtrToUlong(pszProblem), m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  3254. }
  3255. else if (NULL != pszProblem)
  3256. {
  3257. if ('\0' == *pszProblem)
  3258. LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  3259. else
  3260. StrCpyN(m_szOperationProblem, pszProblem, ARRAYSIZE(m_szOperationProblem));
  3261. }
  3262. if (NULL != pszDetails && IS_INTRESOURCE(pszDetails))
  3263. {
  3264. LoadString(g_hLocRes, PtrToUlong(pszDetails), m_szOperationDetails, ARRAYSIZE(m_szOperationDetails));
  3265. }
  3266. else if (NULL != pszDetails)
  3267. {
  3268. if ('\0' == *pszDetails)
  3269. m_szOperationDetails[0] = '\0';
  3270. else
  3271. StrCpyN(m_szOperationDetails, pszDetails, ARRAYSIZE(m_szOperationDetails));
  3272. }
  3273. // If we are currently disconnected, it is unlikely that any additional operations
  3274. // should be sent to the IMAP server: there's likely a connection error or user cancellation.
  3275. if (STORE_E_OPERATION_CANCELED == hr || m_cfsPrevState <= CFSM_STATE_WAITFORCONN)
  3276. dwFlags |= SE_FLAG_FLUSHALL;
  3277. // Fill out the STOREERROR structure
  3278. ZeroMemory(pErrorInfo, sizeof(*pErrorInfo));
  3279. pErrorInfo->hrResult = hr;
  3280. pErrorInfo->uiServerError = 0; // No such thing in the IMAP protocol
  3281. pErrorInfo->hrServerError = S_OK;
  3282. pErrorInfo->dwSocketError = dwSocketError; // Oops, not propagated in IIMAPCallback::OnResponse
  3283. pErrorInfo->pszProblem = m_szOperationProblem;
  3284. pErrorInfo->pszDetails = m_szOperationDetails;
  3285. pErrorInfo->pszAccount = m_rInetServerInfo.szAccount;
  3286. pErrorInfo->pszServer = m_rInetServerInfo.szServerName;
  3287. pErrorInfo->pszUserName = m_rInetServerInfo.szUserName;
  3288. pErrorInfo->pszProtocol = "IMAP";
  3289. pErrorInfo->pszConnectoid = m_rInetServerInfo.szConnectoid;
  3290. pErrorInfo->rasconntype = m_rInetServerInfo.rasconntype;
  3291. pErrorInfo->ixpType = IXP_IMAP;
  3292. pErrorInfo->dwPort = m_rInetServerInfo.dwPort;
  3293. pErrorInfo->fSSL = m_rInetServerInfo.fSSL;
  3294. pErrorInfo->fTrySicily = m_rInetServerInfo.fTrySicily;
  3295. pErrorInfo->dwFlags = dwFlags;
  3296. }
  3297. //***************************************************************************
  3298. // Function: Fill_MESSAGEINFO
  3299. //
  3300. // Purpose:
  3301. // This function is no longer largely based on (shamelessly stolen) code
  3302. // from MsgIn.Cpp's. As Brett rewrote it to use MIMEOLE. Fingers crossed, kids...
  3303. // This function takes a FETCH_CMD_RESULTS_EX struct (which MUST
  3304. // have a header or a body) and fills out a MESSAGEINFO structure based on
  3305. // the information in the header.
  3306. //
  3307. // Arguments:
  3308. // const FETCH_CMD_RESULTS_EX *pFetchResults [in] - contains the results of
  3309. // a FETCH response. This MUST contain either a header or a body.
  3310. // MESSAGEINFO *pMsgInfo [out] - this function fills out the given
  3311. // MESSAGEINFO with the information from the FETCH response. Note that
  3312. // this function zeroes the destination, so the caller need not.
  3313. //
  3314. // Returns:
  3315. // HRESULT indicating success or failure.
  3316. //***************************************************************************
  3317. HRESULT CIMAPSync::Fill_MESSAGEINFO(const FETCH_CMD_RESULTS_EX *pFetchResults,
  3318. MESSAGEINFO *pMsgInfo)
  3319. {
  3320. // Locals
  3321. HRESULT hr = S_OK;
  3322. LPSTR lpsz;
  3323. IMimePropertySet *pPropertySet = NULL;
  3324. IMimeAddressTable *pAddrTable = NULL;
  3325. ADDRESSPROPS rAddress;
  3326. IMSGPRIORITY impPriority;
  3327. PROPVARIANT rVariant;
  3328. LPSTREAM lpstmRFC822;
  3329. TraceCall("CIMAPSync::Fill_MESSAGEINFO");
  3330. IxpAssert(m_cRef > 0);
  3331. IxpAssert(NULL != pFetchResults);
  3332. IxpAssert(NULL != pMsgInfo);
  3333. IxpAssert(TRUE == pFetchResults->bUID);
  3334. // Initialize the destination
  3335. ZeroMemory(pMsgInfo, sizeof(MESSAGEINFO));
  3336. // Fill in fields that need no thought
  3337. pMsgInfo->pszAcctId = PszDupA(m_pszAccountID);
  3338. pMsgInfo->pszAcctName = PszDupA(m_szAccountName);
  3339. // Deal with the easy FETCH_CMD_RESULTS_EX fields, first
  3340. if (pFetchResults->bUID)
  3341. pMsgInfo->idMessage = (MESSAGEID)((ULONG_PTR)pFetchResults->dwUID);
  3342. if (pFetchResults->bMsgFlags)
  3343. pMsgInfo->dwFlags = DwConvertIMAPtoARF(pFetchResults->mfMsgFlags);
  3344. if (pFetchResults->bRFC822Size)
  3345. pMsgInfo->cbMessage = pFetchResults->dwRFC822Size;
  3346. if (pFetchResults->bInternalDate)
  3347. pMsgInfo->ftReceived = pFetchResults->ftInternalDate;
  3348. if (pFetchResults->bEnvelope)
  3349. {
  3350. hr= ReadEnvelopeFields(pMsgInfo, pFetchResults);
  3351. if (FAILED(hr))
  3352. {
  3353. TraceResult(hr);
  3354. goto exit;
  3355. }
  3356. }
  3357. // Now, it's time to parse the header (or partial header, if we only asked for certain fields)
  3358. lpstmRFC822 = (LPSTREAM) pFetchResults->lpFetchCookie2;
  3359. if (NULL == lpstmRFC822)
  3360. {
  3361. if (FALSE == pFetchResults->bEnvelope)
  3362. hr = TraceResult(E_FAIL); // Hmm, no envelope, no header... sounds like failure!
  3363. goto exit;
  3364. }
  3365. hr = MimeOleCreatePropertySet(NULL, &pPropertySet);
  3366. if (FAILED(hr))
  3367. {
  3368. TraceResult(hr);
  3369. goto exit;
  3370. }
  3371. hr = HrRewindStream(lpstmRFC822);
  3372. if (FAILED(hr))
  3373. {
  3374. TraceResult(hr);
  3375. goto exit;
  3376. }
  3377. // call IPS::Load on the header, and get the parsed stuff out.
  3378. hr = pPropertySet->Load(lpstmRFC822);
  3379. if (FAILED(hr))
  3380. {
  3381. TraceResult(hr);
  3382. goto exit;
  3383. }
  3384. // Don't ask for the following basic (non-derived) fields unless we did NOT get an envelope
  3385. if (FALSE == pFetchResults->bEnvelope)
  3386. {
  3387. // Don't bother tracing non-fatal errors, as not all msgs will have all properties
  3388. hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_MESSAGEID), NOFLAGS, &lpsz);
  3389. if (SUCCEEDED(hr))
  3390. {
  3391. pMsgInfo->pszMessageId = PszDupA(lpsz);
  3392. SafeMimeOleFree(lpsz);
  3393. }
  3394. hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_SUBJECT), NOFLAGS, &lpsz);
  3395. if (SUCCEEDED(hr))
  3396. {
  3397. pMsgInfo->pszSubject = PszDupA(lpsz);
  3398. SafeMimeOleFree(lpsz);
  3399. }
  3400. hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_FROM), NOFLAGS, &lpsz);
  3401. TraceError(hr); // Actually, this is odd
  3402. if (SUCCEEDED(hr))
  3403. {
  3404. pMsgInfo->pszFromHeader = PszDupA(lpsz);
  3405. SafeMimeOleFree(lpsz);
  3406. }
  3407. rVariant.vt = VT_FILETIME;
  3408. hr = pPropertySet->GetProp(PIDTOSTR(PID_ATT_SENTTIME), 0, &rVariant);
  3409. if (SUCCEEDED(hr))
  3410. CopyMemory(&pMsgInfo->ftSent, &rVariant.filetime, sizeof(FILETIME));
  3411. }
  3412. // The following fields are not normally supplied via envelope
  3413. // [PaulHi] 6/10/99
  3414. // !!!Note that the IMAP server will not include these properties in the header download
  3415. // unless they are listed in the request string. See cszIMAPFetchNewHdrsI4r1 string
  3416. // declared above!!!
  3417. hr = MimeOleGetPropA(pPropertySet, STR_HDR_XMSOESREC, NOFLAGS, &lpsz);
  3418. TraceError(hr); // Actually, this is odd
  3419. if (SUCCEEDED(hr))
  3420. {
  3421. pMsgInfo->pszMSOESRec = PszDupA(lpsz);
  3422. SafeMimeOleFree(lpsz);
  3423. }
  3424. hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_REFS), NOFLAGS, &lpsz);
  3425. if (SUCCEEDED(hr))
  3426. {
  3427. pMsgInfo->pszReferences = PszDupA(lpsz);
  3428. SafeMimeOleFree(lpsz);
  3429. }
  3430. hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_XREF), NOFLAGS, &lpsz);
  3431. if (SUCCEEDED(hr))
  3432. {
  3433. pMsgInfo->pszXref = PszDupA(lpsz);
  3434. SafeMimeOleFree(lpsz);
  3435. }
  3436. rVariant.vt = VT_UI4;
  3437. hr = pPropertySet->GetProp(PIDTOSTR(PID_ATT_PRIORITY), 0, &rVariant);
  3438. if (SUCCEEDED(hr))
  3439. // Convert IMSGPRIORITY to ARF_PRI_*
  3440. pMsgInfo->wPriority = (WORD)rVariant.ulVal;
  3441. // Make sure every basic (ie, non-derived) string field has SOMETHING
  3442. if (NULL == pMsgInfo->pszMessageId)
  3443. pMsgInfo->pszMessageId = PszDupA(c_szEmpty);
  3444. if (NULL == pMsgInfo->pszSubject)
  3445. pMsgInfo->pszSubject = PszDupA(c_szEmpty);
  3446. if (NULL == pMsgInfo->pszFromHeader)
  3447. pMsgInfo->pszFromHeader = PszDupA(c_szEmpty);
  3448. if (NULL == pMsgInfo->pszReferences)
  3449. pMsgInfo->pszReferences = PszDupA(c_szEmpty);
  3450. if (NULL == pMsgInfo->pszXref)
  3451. pMsgInfo->pszXref = PszDupA (c_szEmpty);
  3452. // Now that every basic string field is non-NULL, calculate DERIVED str fields
  3453. pMsgInfo->pszNormalSubj = SzNormalizeSubject(pMsgInfo->pszSubject);
  3454. if (NULL == pMsgInfo->pszNormalSubj)
  3455. pMsgInfo->pszNormalSubj = pMsgInfo->pszSubject;
  3456. // Only calculate "To" and "From" if we did NOT get an envelope
  3457. if (FALSE == pFetchResults->bEnvelope)
  3458. {
  3459. // Get an address table
  3460. hr = pPropertySet->BindToObject(IID_IMimeAddressTable, (LPVOID *)&pAddrTable);
  3461. if (FAILED(hr))
  3462. {
  3463. TraceResult(hr);
  3464. goto exit;
  3465. }
  3466. // Split "From" field into a display name and email name
  3467. rAddress.dwProps = IAP_FRIENDLY | IAP_EMAIL;
  3468. hr = pAddrTable->GetSender(&rAddress);
  3469. if (SUCCEEDED(hr))
  3470. {
  3471. pMsgInfo->pszDisplayFrom = rAddress.pszFriendly;
  3472. pMsgInfo->pszEmailFrom = rAddress.pszEmail;
  3473. }
  3474. // Split "To" field into a display name and email name
  3475. hr = pAddrTable->GetFormat(IAT_TO, AFT_DISPLAY_FRIENDLY, &lpsz);
  3476. if (SUCCEEDED(hr))
  3477. {
  3478. pMsgInfo->pszDisplayTo = PszDupA(lpsz);
  3479. SafeMimeOleFree(lpsz);
  3480. }
  3481. hr = pAddrTable->GetFormat(IAT_TO, AFT_DISPLAY_EMAIL, &lpsz);
  3482. if (SUCCEEDED(hr))
  3483. {
  3484. pMsgInfo->pszEmailTo = PszDupA(lpsz);
  3485. SafeMimeOleFree(lpsz);
  3486. }
  3487. }
  3488. // If "Newsgroups" field is present, it supercedes the "To" field
  3489. hr = MimeOleGetPropA(pPropertySet, PIDTOSTR(PID_HDR_NEWSGROUPS), NOFLAGS, &lpsz);
  3490. if (SUCCEEDED(hr))
  3491. {
  3492. SafeMemFree(pMsgInfo->pszDisplayTo); // Free what's already there
  3493. pMsgInfo->pszDisplayTo = PszDupA(lpsz);
  3494. SafeMimeOleFree(lpsz);
  3495. pMsgInfo->dwFlags |= ARF_NEWSMSG;
  3496. }
  3497. // Make sure that all derived fields are non-NULL
  3498. if (NULL == pMsgInfo->pszDisplayFrom)
  3499. pMsgInfo->pszDisplayFrom = PszDupA(c_szEmpty);
  3500. if (NULL == pMsgInfo->pszEmailFrom)
  3501. pMsgInfo->pszEmailFrom = PszDupA(c_szEmpty);
  3502. if (NULL == pMsgInfo->pszDisplayTo)
  3503. pMsgInfo->pszDisplayTo = PszDupA(c_szEmpty);
  3504. // OK, if we get to here, we've decided to live with errors. Suppress errors.
  3505. hr = S_OK;
  3506. exit:
  3507. // Cleanup
  3508. SafeRelease(pPropertySet);
  3509. SafeRelease(pAddrTable);
  3510. // Done
  3511. return hr;
  3512. }
  3513. //***************************************************************************
  3514. //***************************************************************************
  3515. HRESULT CIMAPSync::ReadEnvelopeFields(MESSAGEINFO *pMsgInfo,
  3516. const FETCH_CMD_RESULTS_EX *pFetchResults)
  3517. {
  3518. HRESULT hrResult;
  3519. PROPVARIANT rDecoded;
  3520. // (1) Date
  3521. pMsgInfo->ftSent = pFetchResults->ftENVDate;
  3522. // (2) Subject
  3523. rDecoded.vt = VT_LPSTR;
  3524. if (FAILED(MimeOleDecodeHeader(NULL, pFetchResults->pszENVSubject, &rDecoded, NULL)))
  3525. pMsgInfo->pszSubject = PszDupA(pFetchResults->pszENVSubject);
  3526. else
  3527. pMsgInfo->pszSubject = rDecoded.pszVal;
  3528. if (NULL == pMsgInfo->pszSubject)
  3529. {
  3530. hrResult = TraceResult(E_OUTOFMEMORY);
  3531. goto exit;
  3532. }
  3533. // (3) From
  3534. hrResult = ConcatIMAPAddresses(&pMsgInfo->pszDisplayFrom, &pMsgInfo->pszEmailFrom,
  3535. pFetchResults->piaENVFrom);
  3536. if (FAILED(hrResult))
  3537. {
  3538. TraceResult(hrResult);
  3539. goto exit;
  3540. }
  3541. // (4) Sender: IGNORE
  3542. // (5) ReplyTo: IGNORE
  3543. // (6) To
  3544. hrResult = ConcatIMAPAddresses(&pMsgInfo->pszDisplayTo, &pMsgInfo->pszEmailTo,
  3545. pFetchResults->piaENVTo);
  3546. if (FAILED(hrResult))
  3547. {
  3548. TraceResult(hrResult);
  3549. goto exit;
  3550. }
  3551. // (7) Cc: IGNORE
  3552. // (8) Bcc: IGNORE
  3553. // (9) In-Reply-To: IGNORE
  3554. // (10) MessageID
  3555. pMsgInfo->pszMessageId = PszDupA(pFetchResults->pszENVMessageID);
  3556. if (NULL == pMsgInfo->pszMessageId)
  3557. {
  3558. hrResult = TraceResult(E_OUTOFMEMORY);
  3559. goto exit;
  3560. }
  3561. exit:
  3562. return hrResult;
  3563. } // ReadEnvelopeFields
  3564. //***************************************************************************
  3565. //***************************************************************************
  3566. HRESULT CIMAPSync::ConcatIMAPAddresses(LPSTR *ppszDisplay, LPSTR *ppszEmailAddr,
  3567. IMAPADDR *piaIMAPAddr)
  3568. {
  3569. HRESULT hrResult = S_OK;
  3570. CByteStream bstmDisplay;
  3571. CByteStream bstmEmail;
  3572. BOOL fPrependDisplaySeparator = FALSE;
  3573. BOOL fPrependEmailSeparator = FALSE;
  3574. // Initialize output
  3575. if (NULL != ppszDisplay)
  3576. *ppszDisplay = NULL;
  3577. if (NULL != ppszEmailAddr)
  3578. *ppszEmailAddr = NULL;
  3579. // Loop through all IMAP addresses
  3580. while (NULL != piaIMAPAddr)
  3581. {
  3582. // Concatenate current email address to list of email addresses
  3583. // Do email address first to allow substitution of email addr for missing display name
  3584. if (NULL != ppszEmailAddr)
  3585. {
  3586. if (FALSE == fPrependEmailSeparator)
  3587. fPrependEmailSeparator = TRUE;
  3588. else
  3589. {
  3590. hrResult = bstmEmail.Write(c_szSemiColonSpace, 2, NULL);
  3591. if (FAILED(hrResult))
  3592. {
  3593. TraceResult(hrResult);
  3594. goto exit;
  3595. }
  3596. }
  3597. hrResult = ConstructIMAPEmailAddr(bstmEmail, piaIMAPAddr);
  3598. if (FAILED(hrResult))
  3599. {
  3600. TraceResult(hrResult);
  3601. goto exit;
  3602. }
  3603. } // if (NULL != ppszEmailAddr)
  3604. // Concatenate current display name to list of display names
  3605. if (NULL != ppszDisplay)
  3606. {
  3607. PROPVARIANT rDecoded;
  3608. LPSTR pszName;
  3609. int iLen;
  3610. if (FALSE == fPrependDisplaySeparator)
  3611. fPrependDisplaySeparator = TRUE;
  3612. else
  3613. {
  3614. hrResult = bstmDisplay.Write(c_szSemiColonSpace, 2, NULL);
  3615. if (FAILED(hrResult))
  3616. {
  3617. TraceResult(hrResult);
  3618. goto exit;
  3619. }
  3620. }
  3621. PropVariantInit(&rDecoded);
  3622. rDecoded.vt = VT_LPSTR;
  3623. if (FAILED(MimeOleDecodeHeader(NULL, piaIMAPAddr->pszName, &rDecoded, NULL)))
  3624. pszName = StrDupA(piaIMAPAddr->pszName);
  3625. else
  3626. pszName = rDecoded.pszVal;
  3627. if(FAILED(hrResult = MimeOleUnEscapeStringInPlace(pszName)))
  3628. TraceResult(hrResult);
  3629. iLen = lstrlen(pszName);
  3630. if (0 != iLen)
  3631. hrResult = bstmDisplay.Write(pszName, iLen, NULL);
  3632. else
  3633. // Friendly name is not available! Substitute email address
  3634. hrResult = ConstructIMAPEmailAddr(bstmDisplay, piaIMAPAddr);
  3635. if (rDecoded.pszVal)
  3636. MemFree(rDecoded.pszVal); // Probably should be SafeMimeOleFree, but we also ignore above
  3637. if (FAILED(hrResult))
  3638. {
  3639. TraceResult(hrResult);
  3640. goto exit;
  3641. }
  3642. } // if (NULL != ppszDisplay)
  3643. // Advance pointer
  3644. piaIMAPAddr = piaIMAPAddr->pNext;
  3645. } // while
  3646. // Convert stream to buffer for return to caller
  3647. if (NULL != ppszDisplay)
  3648. {
  3649. hrResult = bstmDisplay.HrAcquireStringA(NULL, ppszDisplay, ACQ_DISPLACE);
  3650. if (FAILED(hrResult))
  3651. {
  3652. TraceResult(hrResult);
  3653. goto exit;
  3654. }
  3655. }
  3656. if (NULL != ppszEmailAddr)
  3657. {
  3658. hrResult = bstmEmail.HrAcquireStringA(NULL, ppszEmailAddr, ACQ_DISPLACE);
  3659. if (FAILED(hrResult))
  3660. {
  3661. TraceResult(hrResult);
  3662. goto exit;
  3663. }
  3664. }
  3665. exit:
  3666. return hrResult;
  3667. } // ConcatIMAPAddresses
  3668. //***************************************************************************
  3669. //***************************************************************************
  3670. HRESULT CIMAPSync::ConstructIMAPEmailAddr(CByteStream &bstmOut, IMAPADDR *piaIMAPAddr)
  3671. {
  3672. HRESULT hrResult;
  3673. hrResult = bstmOut.Write(piaIMAPAddr->pszMailbox, lstrlen(piaIMAPAddr->pszMailbox), NULL);
  3674. if (FAILED(hrResult))
  3675. {
  3676. TraceResult(hrResult);
  3677. goto exit;
  3678. }
  3679. hrResult = bstmOut.Write(c_szAt, 1, NULL);
  3680. if (FAILED(hrResult))
  3681. {
  3682. TraceResult(hrResult);
  3683. goto exit;
  3684. }
  3685. hrResult = bstmOut.Write(piaIMAPAddr->pszHost, lstrlen(piaIMAPAddr->pszHost), NULL);
  3686. if (FAILED(hrResult))
  3687. {
  3688. TraceResult(hrResult);
  3689. goto exit;
  3690. }
  3691. exit:
  3692. return hrResult;
  3693. }
  3694. //***************************************************************************
  3695. //***************************************************************************
  3696. HRESULT CIMAPSync::_SyncHeader(void)
  3697. {
  3698. HRESULT hr = S_OK;
  3699. char szFetchArgs[200];
  3700. BOOL fNOOP = TRUE;
  3701. TraceCall("CIMAPSync::_SyncHeader");
  3702. IxpAssert(m_cRef > 0);
  3703. // Look at the flags to determine the next operation
  3704. if (SYNC_FOLDER_NEW_HEADERS & m_dwSyncToDo)
  3705. {
  3706. // Remove this flag, as we are handling it now
  3707. m_dwSyncToDo &= ~(SYNC_FOLDER_NEW_HEADERS);
  3708. // Check if there are any new messages to retrieve
  3709. // Retrieve new headers iff > 0 msgs in this mailbox (Cyrus bug: sending
  3710. // UID FETCH in empty mailbox results in terminated connection)
  3711. // (NSCP v2.0 bug: no EXISTS resp when SELECT issued from selected state)
  3712. if ((m_dwMsgCount > 0 || FALSE == m_fMsgCountValid) &&
  3713. (FALSE == m_fDidFullSync || m_dwNumNewMsgs > 0))
  3714. {
  3715. DWORD dwCapability;
  3716. // No need to send NOOP anymore
  3717. m_dwSyncToDo &= ~(SYNC_FOLDER_NOOP);
  3718. // New messages available! Send FETCH to retrieve new headers
  3719. hr = GetHighestCachedMsgID(m_pFolder, &m_dwHighestCachedUID);
  3720. if (FAILED(hr))
  3721. {
  3722. TraceResult(hr);
  3723. goto exit;
  3724. }
  3725. hr = m_pTransport->Capability(&dwCapability);
  3726. if (SUCCEEDED(dwCapability) && (IMAP_CAPABILITY_IMAP4rev1 & dwCapability))
  3727. wnsprintf(szFetchArgs, ARRAYSIZE(szFetchArgs), cszIMAPFetchNewHdrsI4r1, m_dwHighestCachedUID + 1);
  3728. else
  3729. wnsprintf(szFetchArgs, ARRAYSIZE(szFetchArgs), cszIMAPFetchNewHdrsI4, m_dwHighestCachedUID + 1);
  3730. hr = m_pTransport->Fetch(tidFETCH_NEW_HDRS, NULL, this,
  3731. NULL, USE_UIDS, szFetchArgs); // We always use UIDs
  3732. if (FAILED(hr))
  3733. {
  3734. TraceResult(hr);
  3735. goto exit;
  3736. }
  3737. else
  3738. ResetStatusCounts();
  3739. // Reset progress indicator variables
  3740. m_dwNumHdrsDLed = 0;
  3741. m_dwNumUnreadDLed = 0;
  3742. m_dwNumHdrsToDL = m_dwNumNewMsgs;
  3743. m_dwNumNewMsgs = 0; // We're handling this now
  3744. m_fNewMail = FALSE;
  3745. m_lSyncFolderRefCount += 1;
  3746. fNOOP = FALSE;
  3747. goto exit; // Limit to one operation at a time, exit function now
  3748. }
  3749. }
  3750. if (SYNC_FOLDER_CACHED_HEADERS & m_dwSyncToDo)
  3751. {
  3752. // Remove this flag, as we are handling it now
  3753. m_dwSyncToDo &= ~(SYNC_FOLDER_CACHED_HEADERS);
  3754. // Check if we have any cached headers, and if we've already done flag update
  3755. if (0 == m_dwHighestCachedUID)
  3756. {
  3757. // Either m_dwHighestCachedUID was never loaded, or it really is zero. Check.
  3758. hr = GetHighestCachedMsgID(m_pFolder, &m_dwHighestCachedUID);
  3759. if (FAILED(hr))
  3760. {
  3761. TraceResult(hr);
  3762. goto exit;
  3763. }
  3764. }
  3765. if (FALSE == m_fDidFullSync && 0 != m_dwHighestCachedUID)
  3766. {
  3767. // No need to send NOOP anymore
  3768. m_dwSyncToDo &= ~(SYNC_FOLDER_NOOP);
  3769. wnsprintf(szFetchArgs, ARRAYSIZE(szFetchArgs), cszIMAPFetchCachedFlags, m_dwHighestCachedUID);
  3770. hr = m_pTransport->Fetch(tidFETCH_CACHED_FLAGS, NULL, this,
  3771. NULL, USE_UIDS, szFetchArgs); // We always use UIDs
  3772. if (FAILED(hr))
  3773. {
  3774. TraceResult(hr);
  3775. goto exit;
  3776. }
  3777. else
  3778. ResetStatusCounts();
  3779. m_lSyncFolderRefCount += 1;
  3780. fNOOP = FALSE;
  3781. goto exit; // Limit to one operation at a time, exit function now
  3782. }
  3783. }
  3784. if (SYNC_FOLDER_PURGE_DELETED & m_dwSyncToDo)
  3785. {
  3786. // Remove the purge flag. Also, no need to send NOOP anymore since EXISTS
  3787. // and FETCH responses can be sent during EXPUNGE
  3788. m_dwSyncToDo &= ~(SYNC_FOLDER_PURGE_DELETED | SYNC_FOLDER_NOOP);
  3789. m_dwSyncFolderFlags &= ~(SYNC_FOLDER_PURGE_DELETED); // Not a standing order
  3790. hr = m_pTransport->Expunge(tidEXPUNGE, 0, this);
  3791. if (FAILED(hr))
  3792. {
  3793. TraceResult(hr);
  3794. goto exit;
  3795. }
  3796. else
  3797. ResetStatusCounts();
  3798. fNOOP = FALSE;
  3799. m_lSyncFolderRefCount += 1;
  3800. goto exit; // Limit to one operation at a time, exit function now
  3801. }
  3802. // If we reached this point, ping svr for new mail/cached hdr updates
  3803. // New mail/cached msg updates will be handled like any other unilateral response
  3804. if (SYNC_FOLDER_NOOP & m_dwSyncToDo)
  3805. {
  3806. // Remove these flags, as we are handling it now
  3807. m_dwSyncToDo &= ~(SYNC_FOLDER_NOOP);
  3808. IxpAssert(0 == (m_dwSyncToDo & (SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS)));
  3809. hr = m_pTransport->Noop(tidNOOP, NULL, this);
  3810. if (FAILED(hr))
  3811. {
  3812. TraceResult(hr);
  3813. goto exit;
  3814. }
  3815. else
  3816. ResetStatusCounts();
  3817. fNOOP = FALSE;
  3818. m_lSyncFolderRefCount += 1;
  3819. goto exit; // Limit to one operation at a time, exit function now
  3820. }
  3821. // Check if we had nothing left to do
  3822. if (fNOOP)
  3823. hr = STORE_S_NOOP;
  3824. exit:
  3825. return hr;
  3826. } // _SyncHeader
  3827. //***************************************************************************
  3828. //***************************************************************************
  3829. void CIMAPSync::ResetStatusCounts(void)
  3830. {
  3831. HRESULT hrTemp;
  3832. FOLDERINFO fiFolderInfo;
  3833. // We're about to do a full synchronization, so restore unread counts to
  3834. // pre-STATUS response levels
  3835. hrTemp = m_pStore->GetFolderInfo(m_idSelectedFolder, &fiFolderInfo);
  3836. TraceError(hrTemp);
  3837. if (SUCCEEDED(hrTemp))
  3838. {
  3839. if (0 != fiFolderInfo.dwStatusMsgDelta || 0 != fiFolderInfo.dwStatusUnreadDelta)
  3840. {
  3841. // Make sure that we never cause counts to dip below 0
  3842. if (fiFolderInfo.dwStatusMsgDelta > fiFolderInfo.cMessages)
  3843. fiFolderInfo.dwStatusMsgDelta = fiFolderInfo.cMessages;
  3844. if (fiFolderInfo.dwStatusUnreadDelta > fiFolderInfo.cUnread)
  3845. fiFolderInfo.dwStatusUnreadDelta = fiFolderInfo.cUnread;
  3846. fiFolderInfo.cMessages -= fiFolderInfo.dwStatusMsgDelta;
  3847. fiFolderInfo.cUnread -= fiFolderInfo.dwStatusUnreadDelta;
  3848. fiFolderInfo.dwStatusMsgDelta = 0;
  3849. fiFolderInfo.dwStatusUnreadDelta = 0;
  3850. Assert((LONG)fiFolderInfo.cMessages >= 0);
  3851. Assert((LONG)fiFolderInfo.cUnread >= 0);
  3852. hrTemp = m_pStore->UpdateRecord(&fiFolderInfo);
  3853. TraceError(hrTemp);
  3854. }
  3855. m_pStore->FreeRecord(&fiFolderInfo);
  3856. }
  3857. } // ResetStatusCounts
  3858. //***************************************************************************
  3859. // Function: CheckUIDValidity
  3860. //
  3861. // Purpose:
  3862. // This function checks the value in m_dwUIDValidity against the
  3863. // UIDValidity in the message cache for this folder. If the two match, no
  3864. // action is taken. Otherwise, the message cache is emptied.
  3865. //***************************************************************************
  3866. HRESULT CIMAPSync::CheckUIDValidity(void)
  3867. {
  3868. FOLDERUSERDATA fudUserData;
  3869. HRESULT hr;
  3870. TraceCall("CIMAPSync::CheckUIDValidity");
  3871. IxpAssert(m_cRef > 0);
  3872. // Load in UIDValidity of cache file
  3873. hr = m_pFolder->GetUserData(&fudUserData, sizeof(fudUserData));
  3874. if (FAILED(hr))
  3875. {
  3876. TraceResult(hr);
  3877. goto exit;
  3878. }
  3879. // Is our current cache file invalid?
  3880. if (m_dwUIDValidity == fudUserData.dwUIDValidity)
  3881. goto exit; // We're the same as ever
  3882. // If we reached this point, the UIDValidity has changed
  3883. // Take out the cache
  3884. hr = m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, NULL, NULL, NULL);
  3885. if (FAILED(hr))
  3886. {
  3887. TraceError(hr);
  3888. goto exit;
  3889. }
  3890. // Write the new UIDValidity to the cache
  3891. fudUserData.dwUIDValidity = m_dwUIDValidity;
  3892. hr = m_pFolder->SetUserData(&fudUserData, sizeof(fudUserData));
  3893. if (FAILED(hr))
  3894. {
  3895. TraceError(hr);
  3896. goto exit;
  3897. }
  3898. exit:
  3899. return hr;
  3900. }
  3901. //***************************************************************************
  3902. // Function: SyncDeletedMessages
  3903. //
  3904. // Purpose:
  3905. // This function is called after the message cache is filled with all of
  3906. // the headers on the IMAP server (for this folder). This function deletes
  3907. // all messages from the message cache which no longer exist on the server.
  3908. //
  3909. // Returns:
  3910. // HRESULT indicating success or failure.
  3911. //***************************************************************************
  3912. HRESULT CIMAPSync::SyncDeletedMessages(void)
  3913. {
  3914. HRESULT hr;
  3915. DWORD *pdwMsgSeqNumToUIDArray = NULL;
  3916. DWORD *pdwCurrentServerUID;
  3917. DWORD *pdwLastServerUID;
  3918. ULONG_PTR ulCurrentCachedUID;
  3919. DWORD dwHighestMsgSeqNum;
  3920. HROWSET hRowSet = HROWSET_INVALID;
  3921. HLOCK hLockNotify=NULL;
  3922. MESSAGEINFO miMsgInfo = {0};
  3923. TraceCall("CIMAPSync::SyncDeletedMessages");
  3924. IxpAssert(m_cRef > 0);
  3925. // First, check for case where there are NO messages on server
  3926. hr = m_pTransport->GetMsgSeqNumToUIDArray(&pdwMsgSeqNumToUIDArray, &dwHighestMsgSeqNum);
  3927. if (FAILED(hr))
  3928. {
  3929. TraceResult(hr);
  3930. pdwMsgSeqNumToUIDArray = NULL; // Just in case
  3931. goto exit;
  3932. }
  3933. if (0 == dwHighestMsgSeqNum)
  3934. {
  3935. // No messages on server! Blow away the entire message cache
  3936. hr = m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, NULL, NULL, NULL);
  3937. TraceError(hr);
  3938. goto exit;
  3939. }
  3940. // If we've reached this point, there are messages on the server and thus
  3941. // we must delete messages from the cache which are no longer on server
  3942. hr = m_pFolder->CreateRowset(IINDEX_PRIMARY, NOFLAGS, &hRowSet);
  3943. if (FAILED(hr))
  3944. {
  3945. TraceResult(hr);
  3946. goto exit;
  3947. }
  3948. hr = m_pFolder->QueryRowset(hRowSet, 1, (void **)&miMsgInfo, NULL);
  3949. if (FAILED(hr))
  3950. {
  3951. TraceResult(hr);
  3952. goto exit;
  3953. }
  3954. else if (S_OK != hr)
  3955. {
  3956. // There are 0 messages in cache. Our work here is done
  3957. IxpAssert(S_FALSE == hr);
  3958. goto exit;
  3959. }
  3960. // This forces all notifications to be queued (this is good since you do segmented deletes)
  3961. m_pFolder->LockNotify(0, &hLockNotify);
  3962. // Step through each UID in the cache and delete those which do not exist
  3963. // in our Msg Seq Num -> UID table (which holds all UIDs currently on server)
  3964. pdwCurrentServerUID = pdwMsgSeqNumToUIDArray;
  3965. pdwLastServerUID = pdwMsgSeqNumToUIDArray + dwHighestMsgSeqNum - 1;
  3966. while (S_OK == hr)
  3967. {
  3968. ulCurrentCachedUID = (ULONG_PTR) miMsgInfo.idMessage;
  3969. // Advance pdwCurrentServerUID so its value is always >= ulCurrentCachedUID
  3970. while (pdwCurrentServerUID < pdwLastServerUID &&
  3971. ulCurrentCachedUID > *pdwCurrentServerUID)
  3972. pdwCurrentServerUID += 1;
  3973. // If *pdwCurrentServerUID != ulCurrentCachedUID, the message in our
  3974. // cache has been deleted from the server
  3975. if (ulCurrentCachedUID != *pdwCurrentServerUID)
  3976. {
  3977. MESSAGEIDLIST midList;
  3978. MESSAGEID mid;
  3979. // This message in our cache has been deleted from the server. Nuke it.
  3980. // $REVIEW: Would probably be more efficient if we constructed MESSAGEID list
  3981. // and deleted whole thing at once, but ask me again when I have time
  3982. mid = (MESSAGEID) ulCurrentCachedUID;
  3983. midList.cAllocated = 0;
  3984. midList.cMsgs = 1;
  3985. midList.prgidMsg = &mid;
  3986. hr = m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, &midList, NULL, NULL);
  3987. TraceError(hr); // Record error but otherwise continue
  3988. }
  3989. // Advance current cached UID
  3990. m_pFolder->FreeRecord(&miMsgInfo);
  3991. hr = m_pFolder->QueryRowset(hRowSet, 1, (void **)&miMsgInfo, NULL);
  3992. }
  3993. IxpAssert(pdwCurrentServerUID <= pdwLastServerUID);
  3994. exit:
  3995. m_pFolder->UnlockNotify(&hLockNotify);
  3996. if (HROWSET_INVALID != hRowSet)
  3997. {
  3998. HRESULT hrTemp;
  3999. // Record but otherwise ignore error
  4000. hrTemp = m_pFolder->CloseRowset(&hRowSet);
  4001. TraceError(hrTemp);
  4002. }
  4003. if (NULL != pdwMsgSeqNumToUIDArray)
  4004. MemFree(pdwMsgSeqNumToUIDArray);
  4005. return hr;
  4006. }
  4007. #define CMAX_DELETE_SEARCH_BLOCK 50
  4008. HRESULT CIMAPSync::DeleteHashedFolders(IHashTable *pHash)
  4009. {
  4010. ULONG cFound=0;
  4011. LPVOID *rgpv;
  4012. TraceCall("CIMAPSync::DeleteHashedFolders");
  4013. IxpAssert(m_cRef > 0);
  4014. IxpAssert(NULL != pHash);
  4015. pHash->Reset();
  4016. while (SUCCEEDED(pHash->Next(CMAX_DELETE_SEARCH_BLOCK, &rgpv, &cFound)))
  4017. {
  4018. while(cFound--)
  4019. {
  4020. HRESULT hrTemp;
  4021. hrTemp = DeleteFolderFromCache((FOLDERID)rgpv[cFound], fNON_RECURSIVE);
  4022. TraceError(hrTemp);
  4023. }
  4024. SafeMemFree(rgpv);
  4025. }
  4026. return S_OK;
  4027. }
  4028. //***************************************************************************
  4029. // Function: DeleteFolderFromCache
  4030. //
  4031. // Purpose:
  4032. // This function attempts to delete the specified folder from the
  4033. // folder cache. If the folder is a leaf folder, it may be deleted immediately.
  4034. // If the folder is an internal node, this function marks the folder for
  4035. // deletion, and deletes the internal node when it no longer has children.
  4036. // Regardless of whether the folder node is removed from the folder cache,
  4037. // the message cache for the given folder is blown away.
  4038. //
  4039. // Arguments:
  4040. // FOLDERID idFolder [in] - the folder which you want to delete.
  4041. // BOOL fRecursive [in] - TRUE if we should delete all child folders of the
  4042. // victim. If FALSE, the victim is deleted only if it has no children.
  4043. // Otherwise the victim is marked as \NoSelect and non-existent.
  4044. //
  4045. // Returns:
  4046. // HRESULT indicating success or failure.
  4047. //***************************************************************************
  4048. HRESULT CIMAPSync::DeleteFolderFromCache(FOLDERID idFolder, BOOL fRecursive)
  4049. {
  4050. HRESULT hr;
  4051. HRESULT hrTemp;
  4052. FOLDERINFO fiFolderInfo;
  4053. BOOL fFreeInfo = FALSE;
  4054. TraceCall("CIMAPSync::DeleteFolderFromCache");
  4055. // Check args and codify assumptions
  4056. IxpAssert(m_cRef > 0);
  4057. IxpAssert(FOLDERID_INVALID != idFolder);
  4058. // Get some info about the node
  4059. hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
  4060. if (FAILED(hr))
  4061. {
  4062. if (DB_E_NOTFOUND == hr)
  4063. hr = S_OK; // Deletion target already gone, don't confuse user out w/ err msgs
  4064. else
  4065. TraceResult(hr);
  4066. goto exit;
  4067. }
  4068. fFreeInfo = TRUE;
  4069. // OK, now we can get rid of the foldercache node based on the following rules:
  4070. // 1) Non-listing of an interior node must not remove its inferiors: the interior node
  4071. // just becomes \NoSelect for us, and we delete it once it loses its children.
  4072. // 2) Deletion of a leaf node removes the node and any deleted parents. (If a
  4073. // parent is deleted, we keep it around until it has no kids.)
  4074. // 3) fRecursive TRUE means take no prisoners.
  4075. // Check if we need to recurse on the children
  4076. if (fRecursive)
  4077. {
  4078. IEnumerateFolders *pEnum;
  4079. FOLDERINFO fiChildInfo={0};
  4080. if (SUCCEEDED(m_pStore->EnumChildren(idFolder, fUNSUBSCRIBE, &pEnum)))
  4081. {
  4082. while (S_OK == pEnum->Next(1, &fiChildInfo, NULL))
  4083. {
  4084. hr = DeleteFolderFromCache(fiChildInfo.idFolder, fRecursive);
  4085. if (FAILED(hr))
  4086. {
  4087. TraceResult(hr);
  4088. break;
  4089. }
  4090. m_pStore->FreeRecord(&fiChildInfo);
  4091. }
  4092. m_pStore->FreeRecord(&fiChildInfo);
  4093. pEnum->Release();
  4094. if (FAILED(hr))
  4095. {
  4096. TraceResult(hr);
  4097. goto exit;
  4098. }
  4099. // Re-load the current folder node
  4100. m_pStore->FreeRecord(&fiFolderInfo);
  4101. hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
  4102. if (FAILED(hr))
  4103. {
  4104. TraceResult(hr);
  4105. goto exit;
  4106. }
  4107. }
  4108. }
  4109. // Is this an interior node?
  4110. if (FOLDER_HASCHILDREN & fiFolderInfo.dwFlags)
  4111. {
  4112. IMessageFolder *pFolder;
  4113. // It's an interior node. Awwwww, no nukes... make it \NoSelect,
  4114. // and mark it for deletion as soon as it loses its kids
  4115. fiFolderInfo.dwFlags |= FOLDER_NOSELECT | FOLDER_NONEXISTENT;
  4116. fiFolderInfo.cMessages = 0;
  4117. fiFolderInfo.cUnread = 0;
  4118. fiFolderInfo.dwStatusMsgDelta = 0;
  4119. fiFolderInfo.dwStatusUnreadDelta = 0;
  4120. hr = m_pStore->UpdateRecord(&fiFolderInfo);
  4121. if (FAILED(hr))
  4122. {
  4123. TraceResult(hr);
  4124. goto exit;
  4125. }
  4126. // Plow the associated message cache
  4127. hrTemp = m_pStore->OpenFolder(fiFolderInfo.idFolder, NULL, NOFLAGS, &pFolder);
  4128. TraceError(hrTemp);
  4129. if (SUCCEEDED(hrTemp))
  4130. {
  4131. hrTemp = pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, NULL, NULL, NULL);
  4132. TraceError(hrTemp);
  4133. pFolder->Release();
  4134. }
  4135. }
  4136. else
  4137. {
  4138. // It's a leaf node. Nuke it, AND its family. DeleteLeafFolder fills in
  4139. // fiFolderInfo.idParent for use in RecalculateParentFlags call (no longer called)
  4140. fiFolderInfo.idParent = idFolder;
  4141. hr = DeleteLeafFolder(&fiFolderInfo.idParent);
  4142. if (FAILED(hr))
  4143. {
  4144. TraceResult(hr);
  4145. goto exit;
  4146. }
  4147. }
  4148. exit:
  4149. if (fFreeInfo)
  4150. m_pStore->FreeRecord(&fiFolderInfo);
  4151. return hr;
  4152. }
  4153. //***************************************************************************
  4154. // Function: DeleteLeafFolder
  4155. //
  4156. // Purpose:
  4157. // This function is used by DeleteFolderFromCache to delete a leaf folder.
  4158. // More than just a leaf blower, this function also checks if the parents of
  4159. // the given leaf node can be deleted.
  4160. //
  4161. // The reason we keep folder nodes around even though they haven't been
  4162. // listed is that it is possible on some IMAP servers to create folders whose
  4163. // parents aren't listed. For instance, "CREATE foo/bar" might not create foo,
  4164. // but "foo/bar" will be there. There has to be SOME path to that node, so
  4165. // when foo/bar goes, you can bet that we'll want to get rid of our "foo".
  4166. //
  4167. // Arguments:
  4168. // FOLDERID *pidCurrent [in/out] - pass in the HFOLDER identifying the leaf
  4169. // node to delete. The function returns a pointer to the closest existing
  4170. // ancestor of the deleted node (several parent nodes may be deleted).
  4171. //
  4172. //
  4173. // Returns:
  4174. // HRESULT indicating success or failure.
  4175. //***************************************************************************
  4176. HRESULT CIMAPSync::DeleteLeafFolder(FOLDERID *pidCurrent)
  4177. {
  4178. HRESULT hr;
  4179. BOOL fFirstFolder;
  4180. FOLDERINFO fiFolderInfo;
  4181. // Check args and codify assumptions
  4182. TraceCall("CIMAPSync::DeleteLeafFolder");
  4183. IxpAssert(m_cRef > 0);
  4184. // Initialize variables
  4185. fFirstFolder = TRUE;
  4186. // Loop until the folder is not a deletion candidate
  4187. while (FOLDERID_INVALID != *pidCurrent && FOLDERID_ROOT != *pidCurrent &&
  4188. m_idIMAPServer != *pidCurrent)
  4189. {
  4190. // Get the dirt on this node
  4191. hr = m_pStore->GetFolderInfo(*pidCurrent, &fiFolderInfo);
  4192. if (FAILED(hr))
  4193. {
  4194. TraceResult(hr);
  4195. goto exit;
  4196. }
  4197. // Check if this folder is a deletion candidate. To be a deletion candidate,
  4198. // it must either be the first folder we see (we assume the caller gave us a
  4199. // leaf node to delete), or marked for deletion AND have no children left.
  4200. if (FALSE == fFirstFolder && (0 == (FOLDER_NONEXISTENT & fiFolderInfo.dwFlags) ||
  4201. (FOLDER_HASCHILDREN & fiFolderInfo.dwFlags)))
  4202. {
  4203. m_pStore->FreeRecord(&fiFolderInfo);
  4204. break;
  4205. }
  4206. // We've got some deletion to do
  4207. // Unlink the leaf folder node from its parent folder
  4208. AssertSz(0 == (FOLDER_HASCHILDREN & fiFolderInfo.dwFlags),
  4209. "Hey, what's the idea, orphaning child nodes?");
  4210. hr = m_pStore->DeleteFolder(fiFolderInfo.idFolder,
  4211. DELETE_FOLDER_NOTRASHCAN | DELETE_FOLDER_DELETESPECIAL, NOSTORECALLBACK);
  4212. if (FAILED(hr))
  4213. {
  4214. m_pStore->FreeRecord(&fiFolderInfo);
  4215. TraceResult(hr);
  4216. goto exit;
  4217. }
  4218. // Next stop: your mama
  4219. *pidCurrent = fiFolderInfo.idParent;
  4220. m_pStore->FreeRecord(&fiFolderInfo);
  4221. fFirstFolder = FALSE;
  4222. }
  4223. exit:
  4224. return hr;
  4225. }
  4226. //***************************************************************************
  4227. // Function: AddFolderToCache
  4228. //
  4229. // Purpose:
  4230. // This function saves the given folder (fresh from
  4231. // _OnMailBoxList) to the folder cache. This code used to live
  4232. // in _OnMailBoxList but, that function got too big after I
  4233. // added hierarchy determination code.
  4234. //
  4235. // Arguments:
  4236. // LPSTR pszMailboxName [in] - name of the mailbox as returned by LIST/LSUB
  4237. // IMAP_MBOXFLAGS [in] - flags of mailbox as returned by LIST/LSUB
  4238. // char cHierarchyChar [in] - hierarchy char returned by LIST/LSUB
  4239. // DWORD dwAFTCFlags [in] - Set the following flags:
  4240. // AFTC_SUBSCRIBED if folder is subscribed (eg, returned via LSUB)
  4241. // AFTC_KEEPCHILDRENKNOWN to suppress removal of FOLDER_CHILDRENKNOWN
  4242. // AFTC_NOTSUBSCRIBED if folder is no longer subscribed (NEVER set via
  4243. // LIST, but instead at end of successful UNSUBSCRIBE command)
  4244. //
  4245. // Returns:
  4246. // HRESULT indicating success or failure.
  4247. //***************************************************************************
  4248. HRESULT CIMAPSync::AddFolderToCache(LPSTR pszMailboxName,
  4249. IMAP_MBOXFLAGS imfMboxFlags,
  4250. char cHierarchyChar, DWORD dwAFTCFlags,
  4251. FOLDERID *pFolderID, SPECIALFOLDER sfType)
  4252. {
  4253. HRESULT hr;
  4254. BOOL bResult;
  4255. ADD_HIER_FLDR_OPTIONS ahfo;
  4256. BOOL fValidPrefix;
  4257. TraceCall("CIMAPSync::AddFolderToCache");
  4258. IxpAssert(m_cRef > 0);
  4259. IxpAssert(NULL != pszMailboxName);
  4260. IxpAssert(NULL != pFolderID);
  4261. // Create or find a folder node for this folder name
  4262. // Fill in foldercache props. INBOX is always treated as a subscribed folder
  4263. // Add new IMAP mbox flags, remove all IMAP mbox flags that we're not adding
  4264. ahfo.sfType = sfType;
  4265. ahfo.ffFlagAdd = DwConvertIMAPMboxToFOLDER(imfMboxFlags);
  4266. ahfo.ffFlagRemove = DwConvertIMAPMboxToFOLDER(IMAP_MBOX_ALLFLAGS) & ~(ahfo.ffFlagAdd);
  4267. ahfo.ffFlagRemove |= FOLDER_NONEXISTENT; // Always remove: folder must exist if we listed it
  4268. ahfo.ffFlagRemove |= FOLDER_HIDDEN; // If we listed the folder, we no longer need to hide it
  4269. ahfo.ffFlagRemove |= FOLDER_CREATEONDEMAND; // If we listed the folder, we no longer need to create it
  4270. // Figure out which flags to add and remove
  4271. if (ISFLAGSET(dwAFTCFlags, AFTC_SUBSCRIBED) || FOLDER_INBOX == sfType)
  4272. ahfo.ffFlagAdd |= FOLDER_SUBSCRIBED; // This folder is subscribed
  4273. else if (ISFLAGSET(dwAFTCFlags, AFTC_NOTSUBSCRIBED))
  4274. ahfo.ffFlagRemove |= FOLDER_SUBSCRIBED; // This folder is no longer subscribed
  4275. if (AFTC_NOTRANSLATION & dwAFTCFlags)
  4276. ahfo.ffFlagAdd |= FOLDER_NOTRANSLATEUTF7;
  4277. else
  4278. ahfo.ffFlagRemove |= FOLDER_NOTRANSLATEUTF7;
  4279. if (IMAP_MBOX_NOINFERIORS & imfMboxFlags)
  4280. // NoInferiors folders cannot have children, so we never have to ask
  4281. ahfo.ffFlagAdd |= FOLDER_CHILDRENKNOWN;
  4282. else if (ISFLAGCLEAR(dwAFTCFlags, AFTC_KEEPCHILDRENKNOWN))
  4283. // Remove FOLDER_CHILDRENKNOWN from this fldr so we ask for its chldrn when it's expanded
  4284. ahfo.ffFlagRemove |= FOLDER_CHILDRENKNOWN;
  4285. hr = FindHierarchicalFolderName(pszMailboxName, cHierarchyChar,
  4286. pFolderID, &ahfo);
  4287. if (FAILED(hr))
  4288. {
  4289. TraceResult(hr);
  4290. goto exit;
  4291. }
  4292. AssertSz(FOLDERID_INVALID != *pFolderID, "Hey, what does it take to get a folder handle?");
  4293. exit:
  4294. return hr;
  4295. }
  4296. //***************************************************************************
  4297. // Function: RemovePrefixFromPath
  4298. //
  4299. // Purpose:
  4300. // This function removes the prefix from the given mailbox path. If the
  4301. // given path is a special folder, this function removes all of the special
  4302. // folder path prefix except for the leaf node (eg, "foo/Sent Items/bar"
  4303. // becomes "Sent Items/bar").
  4304. //
  4305. // Arguments:
  4306. // LPSTR pszPrefix [in] - the prefix to strip from pszMailboxName. Note
  4307. // that this may not necessarily be the prefix stripped from pszMailboxName,
  4308. // if we match a special folder path.
  4309. // LPSTR pszMailboxName [in] - the full path to the mailbox, incl prefix
  4310. // char cHierarchyChar [in] - used to interpret pszMailboxName.
  4311. // LPBOOL pfValidPrefix [out] - returns TRUE if this mailbox name has a
  4312. // valid prefix, FALSE otherwise. Pass NULL if not interested.
  4313. // SPECIALFOLDER *psfType [out] - returns SPECIALFOLDER (eg, FOLDER_NOTSPECIAL,
  4314. // FOLDER_INBOX) of folder. Pass NULL if not interested.
  4315. //
  4316. // Returns:
  4317. // LPSTR pointing past the prefix and hierarchy character.
  4318. //***************************************************************************
  4319. LPSTR CIMAPSync::RemovePrefixFromPath(LPSTR pszPrefix, LPSTR pszMailboxName,
  4320. char cHierarchyChar, LPBOOL pfValidPrefix,
  4321. SPECIALFOLDER *psfType)
  4322. {
  4323. LPSTR pszSpecial = NULL;
  4324. LPSTR pszRFP = NULL;
  4325. BOOL fValidPrefix = FALSE;
  4326. SPECIALFOLDER sfType;
  4327. TraceCall("CIMAPSync::RemovePrefixFromPath");
  4328. IxpAssert(INVALID_HIERARCHY_CHAR != cHierarchyChar);
  4329. // Check for special folder path prefixes
  4330. pszSpecial = ImapUtil_GetSpecialFolderType(m_pszAccountID, pszMailboxName,
  4331. cHierarchyChar, pszPrefix, &sfType);
  4332. if (NULL != pszSpecial)
  4333. fValidPrefix = TRUE;
  4334. // If this is a special folder, no need to check for root folder prefix
  4335. if (FOLDER_NOTSPECIAL != sfType)
  4336. {
  4337. IxpAssert(NULL != pszSpecial);
  4338. pszMailboxName = pszSpecial;
  4339. goto exit;
  4340. }
  4341. // Check for the root folder prefix
  4342. if ('\0' != pszPrefix[0] && '\0' != cHierarchyChar)
  4343. {
  4344. int iResult, iPrefixLength;
  4345. // Do case-INSENSITIVE compare (IE5 bug #59121). If we ask for Inbox/* we must be
  4346. // able to handle receipt of INBOX/*. Don't worry about case-sensitive servers since
  4347. // they will never return an RFP of different case than the one we specified
  4348. iPrefixLength = lstrlen(pszPrefix);
  4349. iResult = StrCmpNI(pszMailboxName, pszPrefix, iPrefixLength);
  4350. if (0 == iResult)
  4351. {
  4352. // Prefix name found at front of this mailbox name! Remove it iff
  4353. // it is followed immediately by hierarchy character
  4354. if (cHierarchyChar == pszMailboxName[iPrefixLength])
  4355. {
  4356. pszRFP = pszMailboxName + iPrefixLength + 1; // Point past the hierarchy char
  4357. fValidPrefix = TRUE;
  4358. }
  4359. else if ('\0' == pszMailboxName[iPrefixLength])
  4360. {
  4361. pszRFP = pszMailboxName + iPrefixLength;
  4362. fValidPrefix = TRUE;
  4363. }
  4364. }
  4365. }
  4366. else
  4367. fValidPrefix = TRUE;
  4368. // We basically want to return the shortest mailbox name. For instance, in choosing
  4369. // between "INBOX.foo" and "foo", we should choose "foo"
  4370. IxpAssert(pszMailboxName > NULL && pszRFP >= NULL && pszSpecial >= NULL);
  4371. if (NULL != pszRFP || NULL != pszSpecial)
  4372. {
  4373. IxpAssert(pszRFP >= pszMailboxName || pszSpecial >= pszMailboxName);
  4374. pszMailboxName = max(pszRFP, pszSpecial);
  4375. }
  4376. exit:
  4377. if (NULL != pfValidPrefix)
  4378. *pfValidPrefix = fValidPrefix;
  4379. if (NULL != psfType)
  4380. *psfType = sfType;
  4381. return pszMailboxName;
  4382. }
  4383. //***************************************************************************
  4384. // Function: FindHierarchicalFolderName
  4385. //
  4386. // Purpose:
  4387. // This function takes a mailbox name as returned by LIST/LSUB and
  4388. // determines whether the given mailbox already exists in the folder cache.
  4389. // If so, a handle to the folder is returned. If not, and the fCreate argument
  4390. // is TRUE, then the mailbox and any intermediate nodes are created, and a
  4391. // handle to the mailbox (leaf node) is returned.
  4392. //
  4393. // Arguments:
  4394. // LPSTR lpszFolderPath [in] - the name of the mailbox as returned by
  4395. // a LIST or LSUB response. This should NOT include the prefix!
  4396. // char cHierarchyChar [in] - the hierarchy character used in
  4397. // lpszFolderPath. Used to determine parenthood.
  4398. // FOLDERID *pidTarget [out] - if the function is successful, a handle
  4399. // to the folder is returned here.
  4400. // ADD_HIER_FLDR_OPTIONS pahfoCreateInfo [in] - set to NULL if this function
  4401. // should find the given lpszFolderPath, but NOT create the folder. Pass
  4402. // in a ptr to a ADD_HIER_FLDR_OPTIONS structure if the folder should be
  4403. // created. pahfoCreateInfo defines the dwImapFlags and sftype to use
  4404. // if the folder has to be created.
  4405. //
  4406. // Returns:
  4407. // HRESULT indicating success or failure. If successful, a handle to the
  4408. // desired folder is returned in the pidTarget parameter. There are two
  4409. // possible success results:
  4410. // S_OK - found the folder, did not have to create
  4411. // S_CREATED - folder was successfully created
  4412. //***************************************************************************
  4413. HRESULT CIMAPSync::FindHierarchicalFolderName(LPSTR lpszFolderPath,
  4414. char cHierarchyChar,
  4415. FOLDERID *pidTarget,
  4416. ADD_HIER_FLDR_OPTIONS *pahfoCreateInfo)
  4417. {
  4418. char *pszCurrentFldrName;
  4419. FOLDERID idCurrent, idPrev;
  4420. HRESULT hr;
  4421. LPSTR pszTok;
  4422. LPSTR pszIHateStrTok = NULL;
  4423. char szHierarchyChar[2];
  4424. TraceCall("CIMAPSync::FindHierarchicalFolderName");
  4425. IxpAssert(m_cRef > 0);
  4426. IxpAssert(NULL != lpszFolderPath);
  4427. IxpAssert(NULL != pidTarget);
  4428. // Initialize variables
  4429. *pidTarget = FOLDERID_INVALID;
  4430. hr = S_OK;
  4431. idPrev = FOLDERID_INVALID;
  4432. idCurrent = m_idIMAPServer;
  4433. szHierarchyChar[0] = cHierarchyChar;
  4434. szHierarchyChar[1] = '\0';
  4435. #ifdef DEBUG
  4436. // Make sure this fn is never called with a prefix (DEBUG-ONLY)
  4437. // Note that false alarm is possible, eg, RFP=foo and folder="foo/foo/bar".
  4438. BOOL fValidPrefix;
  4439. LPSTR pszPostPrefix;
  4440. pszPostPrefix = RemovePrefixFromPath(m_szRootFolderPrefix, lpszFolderPath,
  4441. cHierarchyChar, &fValidPrefix, NULL);
  4442. AssertSz(FALSE == fValidPrefix || pszPostPrefix == lpszFolderPath,
  4443. "Make sure you've removed the prefix before calling this fn!");
  4444. #endif // DEBUG
  4445. // Initialize pszCurrentFldrName to point to name of first-level mailbox node
  4446. // $REVIEW: We now need to remove the reference portion of the LIST/LSUB cmd
  4447. // from the mailbox name!
  4448. pszIHateStrTok = StringDup(lpszFolderPath);
  4449. pszTok = pszIHateStrTok;
  4450. pszCurrentFldrName = StrTokEx(&pszTok, szHierarchyChar);
  4451. // Loop through mailbox node names until we hit the leaf node
  4452. while (NULL != pszCurrentFldrName)
  4453. {
  4454. LPSTR pszNextFldrName;
  4455. // Pre-load the NEXT folder node so we know when we are at the leaf node
  4456. pszNextFldrName = StrTokEx(&pszTok, szHierarchyChar);
  4457. // Look for the current folder name
  4458. idPrev = idCurrent;
  4459. hr = GetFolderIdFromName(m_pStore, pszCurrentFldrName, idCurrent, &idCurrent);
  4460. IxpAssert(SUCCEEDED(hr) || FOLDERID_INVALID == idCurrent);
  4461. if (NULL == pahfoCreateInfo)
  4462. {
  4463. if (FOLDERID_INVALID == idCurrent)
  4464. break; // Fldr doesn't exist and user doesn't want to create it
  4465. }
  4466. else
  4467. {
  4468. // Create desired folder, including intermediate nodes
  4469. hr = CreateFolderNode(idPrev, &idCurrent, pszCurrentFldrName,
  4470. pszNextFldrName, cHierarchyChar, pahfoCreateInfo);
  4471. if (FAILED(hr))
  4472. break;
  4473. }
  4474. // Advance to the next folder node name
  4475. pszCurrentFldrName = pszNextFldrName;
  4476. }
  4477. // Return results
  4478. if (SUCCEEDED(hr) && FOLDERID_INVALID != idCurrent)
  4479. {
  4480. *pidTarget = idCurrent;
  4481. }
  4482. else
  4483. {
  4484. IxpAssert(FOLDERID_INVALID == *pidTarget); // We set this at start of fn
  4485. if (SUCCEEDED(hr))
  4486. hr = DB_E_NOTFOUND; // Can't return success
  4487. }
  4488. SafeMemFree(pszIHateStrTok);
  4489. return hr;
  4490. }
  4491. //***************************************************************************
  4492. // Function: CreateFolderNode
  4493. //
  4494. // Purpose:
  4495. // This function is called when creating a new folder in the foldercache.
  4496. // It is called for every node from the root folder and the new folder.
  4497. // This function is responsible for creating the terminal node and any
  4498. // intermediate nodes. If these nodes already exist, this function is
  4499. // responsible for adjusting the FLDR_* flags to reflect the new folder
  4500. // that is about to be added.
  4501. //
  4502. // Arguments:
  4503. // FOLDERID idPrev [in] - FOLDERID to parent of current node.
  4504. // FOLDERID *pidCurrent [in/out] - FOLDERID to current node. If current node
  4505. // exists, this is a valid FOLDERID. If the current node must be created,
  4506. // the value here is FOLDERID_INVALID. In this case, the FOLDERID of the
  4507. // created node is returned here.
  4508. // LPSTR pszCurrentFldrName [in] - the name of the current folder node.
  4509. // LPSTR pszNextFldrName [in] - the name of the next folder node. This is
  4510. // NULL if the current node is the terminal node.
  4511. // char cHierarchyChar [in] - hierarchy character for this folder path.
  4512. // Used to save FLDINFO::bHierarchy.
  4513. // ADD_HIER_FLDR_OPTIONS *pahfoCreateInfo [in] - information used to create
  4514. // the terminal folder node and update all of its parent nodes that
  4515. // already exist.
  4516. //
  4517. // Returns:
  4518. // HRESULT indicating success or failure. S_CREATED means a folder node
  4519. // was created.
  4520. //***************************************************************************
  4521. HRESULT CIMAPSync::CreateFolderNode(FOLDERID idPrev, FOLDERID *pidCurrent,
  4522. LPSTR pszCurrentFldrName,
  4523. LPSTR pszNextFldrName, char cHierarchyChar,
  4524. ADD_HIER_FLDR_OPTIONS *pahfoCreateInfo)
  4525. {
  4526. HRESULT hr = S_OK;
  4527. FOLDERINFO fiFolderInfo;
  4528. BOOL fFreeInfo = FALSE;
  4529. TraceCall("CIMAPSync::CreateFolderNode");
  4530. IxpAssert(NULL != pahfoCreateInfo);
  4531. IxpAssert(0 == (pahfoCreateInfo->ffFlagAdd & pahfoCreateInfo->ffFlagRemove));
  4532. // If current folder name not found, we have to create it
  4533. if (FOLDERID_INVALID == *pidCurrent)
  4534. {
  4535. // Initialize
  4536. ZeroMemory(&fiFolderInfo, sizeof(fiFolderInfo));
  4537. // FIRST: Add folder to folder cache
  4538. // Fill out a folderinfo structure (just use it as a scratchpad)
  4539. fiFolderInfo.idParent = idPrev;
  4540. fiFolderInfo.pszName = pszCurrentFldrName;
  4541. fiFolderInfo.bHierarchy = cHierarchyChar;
  4542. // If this is the last folder node name (ie, leaf node), use the
  4543. // IMAP flags returned via the LIST/LSUB, and use the supplied
  4544. // special folder type
  4545. if (NULL == pszNextFldrName)
  4546. {
  4547. fiFolderInfo.tySpecial = pahfoCreateInfo->sfType;
  4548. fiFolderInfo.dwFlags |= pahfoCreateInfo->ffFlagAdd;
  4549. fiFolderInfo.dwFlags &= ~(pahfoCreateInfo->ffFlagRemove);
  4550. if (fiFolderInfo.tySpecial == FOLDER_INBOX)
  4551. fiFolderInfo.dwFlags |= FOLDER_DOWNLOADALL;
  4552. }
  4553. else
  4554. {
  4555. // Otherwise, here are the defaults
  4556. // Non-listed folders are \NoSelect by default, and candidates for deletion
  4557. fiFolderInfo.dwFlags = FOLDER_NOSELECT | FOLDER_NONEXISTENT;
  4558. fiFolderInfo.tySpecial = FOLDER_NOTSPECIAL;
  4559. }
  4560. // Add folder to folder cache
  4561. hr = m_pStore->CreateFolder(NOFLAGS, &fiFolderInfo, NULL);
  4562. if (FAILED(hr))
  4563. {
  4564. TraceResult(hr);
  4565. goto exit;
  4566. }
  4567. *pidCurrent = fiFolderInfo.idFolder;
  4568. hr = S_CREATED; // Tell the user we created this folder
  4569. }
  4570. else if (NULL == pszNextFldrName)
  4571. {
  4572. DWORD dwFlagsChanged = 0;
  4573. BOOL fChanged = FALSE;
  4574. // Folder exists, check that its flags are correct
  4575. hr = m_pStore->GetFolderInfo(*pidCurrent, &fiFolderInfo);
  4576. if (FAILED(hr))
  4577. {
  4578. TraceResult(hr);
  4579. goto exit;
  4580. }
  4581. fFreeInfo = TRUE;
  4582. if (fiFolderInfo.bHierarchy != cHierarchyChar)
  4583. {
  4584. AssertSz(INVALID_HIERARCHY_CHAR == (char) fiFolderInfo.bHierarchy, "What's YOUR excuse?");
  4585. fiFolderInfo.bHierarchy = cHierarchyChar;
  4586. fChanged = TRUE;
  4587. }
  4588. if (NULL == pszNextFldrName && (fiFolderInfo.tySpecial != pahfoCreateInfo->sfType ||
  4589. (fiFolderInfo.dwFlags & pahfoCreateInfo->ffFlagAdd) != pahfoCreateInfo->ffFlagAdd ||
  4590. (fiFolderInfo.dwFlags & pahfoCreateInfo->ffFlagRemove) != 0))
  4591. {
  4592. DWORD dwFlagAddChange;
  4593. DWORD dwFlagRemoveChange;
  4594. // The terminal folder node exists, set everything given via pahfoCreateInfo
  4595. // Check if anything changed, first
  4596. if (pahfoCreateInfo->sfType == FOLDER_INBOX &&
  4597. fiFolderInfo.tySpecial != pahfoCreateInfo->sfType)
  4598. fiFolderInfo.dwFlags |= FOLDER_DOWNLOADALL;
  4599. fiFolderInfo.tySpecial = pahfoCreateInfo->sfType;
  4600. // Figure out which flags changed so we know if we need to recalculate parents
  4601. dwFlagAddChange = (fiFolderInfo.dwFlags & pahfoCreateInfo->ffFlagAdd) ^
  4602. pahfoCreateInfo->ffFlagAdd;
  4603. dwFlagRemoveChange = (~(fiFolderInfo.dwFlags) & pahfoCreateInfo->ffFlagRemove) ^
  4604. pahfoCreateInfo->ffFlagRemove;
  4605. dwFlagsChanged = dwFlagAddChange | dwFlagRemoveChange;
  4606. fiFolderInfo.dwFlags |= pahfoCreateInfo->ffFlagAdd;
  4607. fiFolderInfo.dwFlags &= ~(pahfoCreateInfo->ffFlagRemove);
  4608. fChanged = TRUE;
  4609. }
  4610. // Set the folder properties
  4611. if (fChanged)
  4612. {
  4613. hr = m_pStore->UpdateRecord(&fiFolderInfo);
  4614. if (FAILED(hr))
  4615. {
  4616. TraceResult(hr);
  4617. goto exit;
  4618. }
  4619. }
  4620. }
  4621. exit:
  4622. if (fFreeInfo)
  4623. m_pStore->FreeRecord(&fiFolderInfo);
  4624. return hr;
  4625. }
  4626. //***************************************************************************
  4627. // Function: SetTranslationMode
  4628. //
  4629. // Purpose:
  4630. // This function enables or disables mailbox translation in IIMAPTransport2,
  4631. // depending on whether the FOLDER_NOTRANSLATEUTF7 flags is set for this folder.
  4632. //
  4633. // Returns:
  4634. // HRESULT indicating success or failure. Success codes include:
  4635. // S_OK - mailbox translation has been successfully enabled.
  4636. // S_FALSE - mailbox translation has been successfully disabled.
  4637. //***************************************************************************
  4638. HRESULT CIMAPSync::SetTranslationMode(FOLDERID idFolderID)
  4639. {
  4640. HRESULT hrResult = S_OK;
  4641. FOLDERINFO fiFolderInfo = {0};
  4642. DWORD dwTranslateFlags;
  4643. BOOL fTranslate = TRUE;
  4644. BOOL fFreeInfo = FALSE;
  4645. TraceCall("CIMAPSync::SetTranslationMode");
  4646. // Check for FOLDERID_INVALID (we get this during folder lists)
  4647. // If FOLDERID_INVALID, assume we want to translate everything: leave fiFolderInfo at zero
  4648. if (FOLDERID_INVALID != idFolderID)
  4649. {
  4650. hrResult = m_pStore->GetFolderInfo(idFolderID, &fiFolderInfo);
  4651. if (FAILED(hrResult))
  4652. {
  4653. TraceResult(hrResult);
  4654. goto exit;
  4655. }
  4656. fFreeInfo = TRUE;
  4657. }
  4658. else
  4659. {
  4660. Assert(0 == fiFolderInfo.dwFlags);
  4661. }
  4662. fTranslate = TRUE;
  4663. dwTranslateFlags = IMAP_MBOXXLATE_DEFAULT | IMAP_MBOXXLATE_VERBATIMOK | IMAP_MBOXXLATE_RETAINCP;
  4664. if (fiFolderInfo.dwFlags & FOLDER_NOTRANSLATEUTF7)
  4665. {
  4666. fTranslate = FALSE;
  4667. dwTranslateFlags |= IMAP_MBOXXLATE_DISABLE;
  4668. dwTranslateFlags &= ~(IMAP_MBOXXLATE_DEFAULT);
  4669. }
  4670. hrResult = m_pTransport->SetDefaultCP(dwTranslateFlags, 0);
  4671. if (FAILED(hrResult))
  4672. {
  4673. TraceResult(hrResult);
  4674. goto exit;
  4675. }
  4676. exit:
  4677. if (fFreeInfo)
  4678. m_pStore->FreeRecord(&fiFolderInfo);
  4679. if (SUCCEEDED(hrResult))
  4680. hrResult = (fTranslate ? S_OK : S_FALSE);
  4681. return hrResult;
  4682. } // SetTranslationMode
  4683. //***************************************************************************
  4684. //***************************************************************************
  4685. BOOL CIMAPSync::isUSASCIIOnly(LPCSTR pszFolderName)
  4686. {
  4687. LPCSTR psz;
  4688. BOOL fUSASCII = TRUE;
  4689. psz = pszFolderName;
  4690. while ('\0' != *psz)
  4691. {
  4692. if (0 != (*psz & 0x80))
  4693. {
  4694. fUSASCII = FALSE;
  4695. break;
  4696. }
  4697. psz += 1;
  4698. }
  4699. return fUSASCII;
  4700. } // isUSASCIIOnly
  4701. //***************************************************************************
  4702. //***************************************************************************
  4703. HRESULT CIMAPSync::CheckFolderNameValidity(LPCSTR pszName)
  4704. {
  4705. HRESULT hrResult = S_OK;
  4706. if (NULL == pszName || '\0' == *pszName)
  4707. {
  4708. hrResult = TraceResult(E_INVALIDARG);
  4709. goto exit;
  4710. }
  4711. // Figure out what our root hierarchy character is: assume that server does not
  4712. // support multiple hierarchy characters
  4713. if (INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar)
  4714. {
  4715. hrResult = LoadSaveRootHierarchyChar(fLOAD_HC);
  4716. if (FAILED(hrResult))
  4717. {
  4718. TraceResult(hrResult);
  4719. hrResult = S_OK; // We can't say if this is valid or not, so just assume it is
  4720. goto exit;
  4721. }
  4722. }
  4723. if ('\0' == m_cRootHierarchyChar || INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar)
  4724. goto exit; // Anything goes!
  4725. while ('\0' != *pszName)
  4726. {
  4727. // No hierarchy characters are allowed in the folder name, except at the very end
  4728. if (m_cRootHierarchyChar == *pszName && '\0' != *(pszName + 1))
  4729. {
  4730. // Figure out which HRESULT to use (we need to bring up the correct text)
  4731. switch (m_cRootHierarchyChar)
  4732. {
  4733. case '/':
  4734. hrResult = STORE_E_IMAP_HC_NOSLASH;
  4735. break;
  4736. case '\\':
  4737. hrResult = STORE_E_IMAP_HC_NOBACKSLASH;
  4738. break;
  4739. case '.':
  4740. hrResult = STORE_E_IMAP_HC_NODOT;
  4741. break;
  4742. default:
  4743. hrResult = STORE_E_IMAP_HC_NOHC;
  4744. break;
  4745. }
  4746. TraceResult(hrResult);
  4747. goto exit;
  4748. }
  4749. // Advance pointer
  4750. pszName += 1;
  4751. }
  4752. exit:
  4753. return hrResult;
  4754. }
  4755. //***************************************************************************
  4756. // Function: RenameFolderHelper
  4757. //
  4758. // Purpose:
  4759. // This function is called by RenameFolder. This function is responsible
  4760. // for issuing the RENAME command for the folder which is to be renamed.
  4761. // If the folder to be renamed does not actually exist (eg, Cyrus server),
  4762. // this function recurses on the child folders until a real folder is found.
  4763. //
  4764. // Arguments:
  4765. //
  4766. // Returns:
  4767. // HRESULT indicating success or failure.
  4768. //***************************************************************************
  4769. HRESULT CIMAPSync::RenameFolderHelper(FOLDERID idFolder,
  4770. LPSTR pszFolderPath,
  4771. char cHierarchyChar,
  4772. LPSTR pszNewFolderPath)
  4773. {
  4774. HRESULT hr;
  4775. CRenameFolderInfo *pRenameInfo = NULL;
  4776. FOLDERINFO fiFolderInfo;
  4777. IEnumerateFolders *pFldrEnum = NULL;
  4778. BOOL fFreeInfo = FALSE;
  4779. TraceCall("CIMAPSync::RenameFolderHelper");
  4780. IxpAssert(m_cRef > 0);
  4781. // Check if the folder actually exists on the IMAP server
  4782. hr = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
  4783. if (FAILED(hr))
  4784. {
  4785. TraceResult(hr);
  4786. goto exit;
  4787. }
  4788. // If current folder doesn't exist, recurse rename cmd on child folders
  4789. fFreeInfo = TRUE;
  4790. if (fiFolderInfo.dwFlags & FOLDER_NONEXISTENT) {
  4791. FOLDERINFO fiChildFldrInfo;
  4792. // Perform rename on folder nodes which EXIST: recurse through children
  4793. hr = m_pStore->EnumChildren(idFolder, fUNSUBSCRIBE, &pFldrEnum);
  4794. if (FAILED(hr))
  4795. {
  4796. TraceResult(hr);
  4797. goto exit;
  4798. }
  4799. hr = pFldrEnum->Next(1, &fiChildFldrInfo, NULL);
  4800. if (FAILED(hr))
  4801. {
  4802. TraceResult(hr);
  4803. goto exit;
  4804. }
  4805. while (S_OK == hr)
  4806. {
  4807. LPSTR pszOldPath, pszNewPath;
  4808. DWORD dwLeafFolderLen, dwFolderPathLen, dwNewFolderPathLen;
  4809. DWORD cchOldPath, cchNewPath;
  4810. BOOL fResult;
  4811. CHAR szHierarchyStr[2];
  4812. szHierarchyStr[0] = cHierarchyChar;
  4813. szHierarchyStr[1] = 0;
  4814. // Calculate string sizes, + 2 for HC and null-term
  4815. dwLeafFolderLen = lstrlen(fiChildFldrInfo.pszName);
  4816. dwFolderPathLen = lstrlen(pszFolderPath);
  4817. dwNewFolderPathLen = lstrlen(pszNewFolderPath);
  4818. // Allocate space
  4819. cchOldPath = dwFolderPathLen + dwLeafFolderLen + 2;
  4820. fResult = MemAlloc((void **)&pszOldPath, cchOldPath * sizeof(pszOldPath[0]));
  4821. if (FALSE == fResult)
  4822. {
  4823. m_pStore->FreeRecord(&fiChildFldrInfo);
  4824. hr = TraceResult(E_OUTOFMEMORY);
  4825. goto exit;
  4826. }
  4827. cchNewPath = dwNewFolderPathLen + dwLeafFolderLen + 2;
  4828. fResult = MemAlloc((void **)&pszNewPath, cchNewPath * sizeof(pszNewPath[0]));
  4829. if (FALSE == fResult)
  4830. {
  4831. MemFree(pszOldPath);
  4832. m_pStore->FreeRecord(&fiChildFldrInfo);
  4833. hr = TraceResult(E_OUTOFMEMORY);
  4834. goto exit;
  4835. }
  4836. // Append current child's name to current path, new path
  4837. StrCpyN(pszOldPath, pszFolderPath, cchOldPath);
  4838. StrCatBuff(pszOldPath, szHierarchyStr, cchOldPath);
  4839. StrCatBuff(pszOldPath, fiChildFldrInfo.pszName, cchOldPath);
  4840. StrCpyN(pszNewPath, pszNewFolderPath, cchNewPath);
  4841. StrCatBuff(pszNewPath, szHierarchyStr, cchNewPath);
  4842. StrCatBuff(pszNewPath, fiChildFldrInfo.pszName, cchNewPath);
  4843. // Recurse into the children, in hopes of finding an existing folder
  4844. hr = RenameFolderHelper(fiChildFldrInfo.idFolder, pszOldPath, cHierarchyChar, pszNewPath);
  4845. MemFree(pszOldPath);
  4846. MemFree(pszNewPath);
  4847. if (FAILED(hr))
  4848. {
  4849. m_pStore->FreeRecord(&fiChildFldrInfo);
  4850. TraceResult(hr);
  4851. goto exit;
  4852. }
  4853. // Load in the next child folder
  4854. m_pStore->FreeRecord(&fiChildFldrInfo);
  4855. hr = pFldrEnum->Next(1, &fiChildFldrInfo, NULL);
  4856. if (FAILED(hr))
  4857. {
  4858. TraceResult(hr);
  4859. goto exit;
  4860. }
  4861. } // while (S_OK == hr)
  4862. goto exit; // We don't attempt to rename non-existent folders
  4863. } // if (fiFolderInfo.dwImapFlags & FOLDER_NONEXISTENT)
  4864. // Create a CRenameFolderInfo structure
  4865. pRenameInfo = new CRenameFolderInfo;
  4866. if (NULL == pRenameInfo)
  4867. {
  4868. hr = TraceResult(E_OUTOFMEMORY);
  4869. goto exit;
  4870. }
  4871. // Fill in all the fields
  4872. pRenameInfo->pszFullFolderPath = StringDup(pszFolderPath);
  4873. pRenameInfo->cHierarchyChar = cHierarchyChar;
  4874. pRenameInfo->pszNewFolderPath = StringDup(pszNewFolderPath);
  4875. pRenameInfo->idRenameFolder = idFolder;
  4876. // Send the RENAME command
  4877. pRenameInfo->pszRenameCmdOldFldrPath = StringDup(pszFolderPath);
  4878. hr = _EnqueueOperation(tidRENAME, (LPARAM)pRenameInfo, icRENAME_COMMAND,
  4879. pRenameInfo->pszNewFolderPath, uiNORMAL_PRIORITY);
  4880. if (FAILED(hr))
  4881. {
  4882. TraceResult(hr);
  4883. goto exit;
  4884. }
  4885. exit:
  4886. if (fFreeInfo)
  4887. m_pStore->FreeRecord(&fiFolderInfo);
  4888. if (NULL != pRenameInfo)
  4889. pRenameInfo->Release();
  4890. if (NULL != pFldrEnum)
  4891. pFldrEnum->Release();
  4892. return hr;
  4893. } // RenameFolderHelper
  4894. //***************************************************************************
  4895. // Function: RenameTreeTraversal
  4896. //
  4897. // Purpose:
  4898. // This function performs the requested operation on all child folders of
  4899. // the rename folder (specified in pRenameInfo->hfRenameFolder). For example,
  4900. // the tidRENAMESUBSCRIBE operation indicates that the entire renamed folder
  4901. // hierarchy should be subscribed.
  4902. //
  4903. // Arguments:
  4904. // WPARAM wpOperation [in] - identifies the operation to perform on the
  4905. // rename hierarchy. Current operations include:
  4906. // tidRENAMESUBSCRIBE - subscribe new (renamed) folder hierarchy
  4907. // tidRENAMESUBSCRIBE_AGAIN - same as tidRENAMESUBSCRIBE
  4908. // tidRENAMERENAME - issue individual RENAME's for all old child folders
  4909. // (simulates an atomic rename)
  4910. // tidRENAMELIST - list the FIRST child of the rename folder.
  4911. // tidRENAMEUNSUBSCRIBE - unsubscribe old folder hierarchy.
  4912. //
  4913. // CRenameFolderInfo [in] - the CRenameFolderInfo class associated with
  4914. // the RENAME operation.
  4915. // BOOL fIncludeRenameFolder [in] - TRUE if the rename folder (top node)
  4916. // should be included in the operation, otherwise FALSE.
  4917. //
  4918. // Returns:
  4919. // HRESULT indicating success or failure. S_FALSE is a possible result,
  4920. // indicating that recursion has occurred in RenameTreeTraversalHelper.
  4921. //***************************************************************************
  4922. HRESULT CIMAPSync::RenameTreeTraversal(WPARAM wpOperation,
  4923. CRenameFolderInfo *pRenameInfo,
  4924. BOOL fIncludeRenameFolder)
  4925. {
  4926. HRESULT hrResult;
  4927. LPSTR pszCurrentPath;
  4928. DWORD dwSizeOfCurrentPath;
  4929. FOLDERINFO fiFolderInfo;
  4930. BOOL fFreeInfo = FALSE;
  4931. TraceCall("CIMAPSync::RenameTreeTraversal");
  4932. IxpAssert(m_cRef > 0);
  4933. // Construct the path name to renamed folder's parent, based on operation
  4934. if (tidRENAMESUBSCRIBE == wpOperation ||
  4935. tidRENAMESUBSCRIBE_AGAIN == wpOperation ||
  4936. tidRENAMERENAME == wpOperation)
  4937. pszCurrentPath = pRenameInfo->pszNewFolderPath;
  4938. else
  4939. pszCurrentPath = pRenameInfo->pszFullFolderPath;
  4940. dwSizeOfCurrentPath = lstrlen(pszCurrentPath);
  4941. // We need to get some details about renamed folder node to start the recursion
  4942. hrResult = m_pStore->GetFolderInfo(pRenameInfo->idRenameFolder, &fiFolderInfo);
  4943. if (FAILED(hrResult))
  4944. {
  4945. TraceResult(hrResult);
  4946. goto exit;
  4947. }
  4948. fFreeInfo = TRUE;
  4949. // Start the mayhem
  4950. hrResult = RenameTreeTraversalHelper(wpOperation, pRenameInfo, pszCurrentPath,
  4951. dwSizeOfCurrentPath, fIncludeRenameFolder, &fiFolderInfo);
  4952. if (FAILED(hrResult))
  4953. {
  4954. TraceResult(hrResult);
  4955. goto exit;
  4956. }
  4957. exit:
  4958. if (fFreeInfo)
  4959. m_pStore->FreeRecord(&fiFolderInfo);
  4960. return hrResult;
  4961. } // RenameTreeTraversal
  4962. //***************************************************************************
  4963. // Function: RenameTreeTraversalHelper
  4964. //
  4965. // Purpose:
  4966. // This function actually does the work for RenameTreeTraversal. This
  4967. // function is separate so that it can perform the necessary recursion to
  4968. // execute the desired operation on every child folder of the rename folder.
  4969. //
  4970. // Arguments:
  4971. // WPARAM wpOperation [in] - same as for RenameTreeTraversal.
  4972. // CRenameFolderInfo [in/out] - same as for RenameTreeTraversal. Member
  4973. // variables of this class are updated as required in this function
  4974. // (for instance, iNumListRespExpected is incremented for each LIST sent).
  4975. // LPSTR pszCurrentFldrPath [in/out] - a string describing the full path to
  4976. // the current folder. The first call to this function (from Rename-
  4977. // TreeTraversal) is a full path to the rename folder. This function
  4978. // modifies this buffer (adds leaf node names) as needed.
  4979. // DWORD dwLengthOfCurrentPath [in] - length of pszCurrentFldrPath.
  4980. // BOOL fIncludeThisFolder [in] - TRUE if this function should perform
  4981. // the requested operation on the current node. Otherwise, FALSE.
  4982. // FOLDERINFO *pfiCurrentFldrInfo [in] - contains information about the
  4983. // current folder.
  4984. //
  4985. // Returns:
  4986. // HRESULT indicating success or failure. S_FALSE is a possible return
  4987. // result, typically indicating that recursion has taken place.
  4988. //***************************************************************************
  4989. HRESULT CIMAPSync::RenameTreeTraversalHelper(WPARAM wpOperation,
  4990. CRenameFolderInfo *pRenameInfo,
  4991. LPSTR pszCurrentFldrPath,
  4992. DWORD dwLengthOfCurrentPath,
  4993. BOOL fIncludeThisFolder,
  4994. FOLDERINFO *pfiCurrentFldrInfo)
  4995. {
  4996. HRESULT hrResult = S_OK;
  4997. FOLDERINFO fiFolderInfo;
  4998. IEnumerateFolders *pFldrEnum = NULL;
  4999. TraceCall("CIMAPSync::RenameTreeTraversalHelper");
  5000. IxpAssert(m_cRef > 0);
  5001. // Execute the requested operation, if current folder is not suppressed
  5002. // and if current folder actually exists
  5003. if (fIncludeThisFolder && 0 == (pfiCurrentFldrInfo->dwFlags & FOLDER_NONEXISTENT))
  5004. {
  5005. switch (wpOperation)
  5006. {
  5007. case tidRENAMESUBSCRIBE:
  5008. case tidRENAMESUBSCRIBE_AGAIN:
  5009. hrResult = _EnqueueOperation(wpOperation, (LPARAM) pRenameInfo,
  5010. icSUBSCRIBE_COMMAND, pszCurrentFldrPath, uiNORMAL_PRIORITY);
  5011. if (FAILED(hrResult))
  5012. {
  5013. TraceResult(hrResult);
  5014. goto exit;
  5015. }
  5016. pRenameInfo->iNumSubscribeRespExpected += 1;
  5017. break; // case tidRENAMESUBSCRIBE
  5018. case tidRENAMELIST:
  5019. // This operation is special-cased to send only ONE list command, a list cmd
  5020. // for the first child fldr. The reason this operation is HERE is because this
  5021. // operation used to list ALL the child fldrs, until I found that IIMAPTransport
  5022. // couldn't resolve the ambiguities. (IIMAPTransport will eventually get queuing).
  5023. IxpAssert(0 == pRenameInfo->iNumListRespExpected); // Send only ONE list cmd!
  5024. hrResult = _EnqueueOperation(tidRENAMELIST, (LPARAM) pRenameInfo,
  5025. icLIST_COMMAND, pszCurrentFldrPath, uiNORMAL_PRIORITY);
  5026. if (FAILED(hrResult))
  5027. {
  5028. TraceResult(hrResult);
  5029. goto exit;
  5030. }
  5031. pRenameInfo->iNumListRespExpected += 1;
  5032. goto exit; // Do not recurse any further into the folder hierarchy
  5033. break; // case tidRENAMELIST
  5034. case tidRENAMEUNSUBSCRIBE:
  5035. hrResult = _EnqueueOperation(tidRENAMEUNSUBSCRIBE, (LPARAM) pRenameInfo,
  5036. icUNSUBSCRIBE_COMMAND, pszCurrentFldrPath, uiNORMAL_PRIORITY);
  5037. if (FAILED(hrResult))
  5038. {
  5039. TraceResult(hrResult);
  5040. goto exit;
  5041. }
  5042. pRenameInfo->iNumUnsubscribeRespExpected += 1;
  5043. break; // case tidRENAMEUNSUBSCRIBE
  5044. case tidRENAMERENAME: {
  5045. LPSTR pszRenameCmdOldFldrPath;
  5046. DWORD cchFullFolderPathLen, cchLeafNodeLen;
  5047. LPSTR pszOldFldrPath;
  5048. BOOL fResult;
  5049. // Allocate a buffer for old folder path
  5050. cchFullFolderPathLen = lstrlen(pRenameInfo->pszFullFolderPath);
  5051. cchLeafNodeLen = lstrlen(RemovePrefixFromPath(
  5052. pRenameInfo->pszNewFolderPath, pszCurrentFldrPath,
  5053. pRenameInfo->cHierarchyChar, NULL, NULL));
  5054. DWORD cchSizeOldFldrPath = (cchFullFolderPathLen + cchLeafNodeLen + 2);
  5055. fResult = MemAlloc((void **)&pszOldFldrPath, cchSizeOldFldrPath * sizeof(pszOldFldrPath[0]));
  5056. if (FALSE == fResult)
  5057. {
  5058. hrResult = TraceResult(E_OUTOFMEMORY); // Abort, folder paths aren't getting shorter
  5059. goto exit;
  5060. }
  5061. // Construct old folder path (MUST be below rename folder level)
  5062. MemFree(pRenameInfo->pszRenameCmdOldFldrPath);
  5063. StrCpyN(pszOldFldrPath, pRenameInfo->pszFullFolderPath, cchSizeOldFldrPath);
  5064. *(pszOldFldrPath + cchFullFolderPathLen) = pfiCurrentFldrInfo->bHierarchy;
  5065. StrCatBuff(pszOldFldrPath,
  5066. RemovePrefixFromPath(pRenameInfo->pszNewFolderPath, pszCurrentFldrPath, pRenameInfo->cHierarchyChar, NULL, NULL), cchSizeOldFldrPath);
  5067. pRenameInfo->pszRenameCmdOldFldrPath = pszOldFldrPath;
  5068. hrResult = _EnqueueOperation(tidRENAMERENAME, (LPARAM) pRenameInfo,
  5069. icRENAME_COMMAND, pszCurrentFldrPath, uiNORMAL_PRIORITY);
  5070. if (FAILED(hrResult))
  5071. {
  5072. TraceResult(hrResult);
  5073. goto exit;
  5074. }
  5075. pRenameInfo->iNumRenameRespExpected += 1;
  5076. } // case todRENAMERENAME
  5077. break; // case tidRENAMERENAME
  5078. default:
  5079. AssertSz(FALSE, "I don't know how to perform this operation.");
  5080. hrResult = TraceResult(E_FAIL);
  5081. goto exit;
  5082. } // switch (wpOperation)
  5083. } // if (fIncludeThisFolder)
  5084. // Now, recurse upon all my children, if there are any
  5085. if (0 == (FOLDER_HASCHILDREN & pfiCurrentFldrInfo->dwFlags))
  5086. goto exit; // We're done!
  5087. // Initialize the child-traversal-loop
  5088. hrResult = m_pStore->EnumChildren(pfiCurrentFldrInfo->idFolder, fUNSUBSCRIBE, &pFldrEnum);
  5089. if (FAILED(hrResult))
  5090. {
  5091. TraceResult(hrResult);
  5092. goto exit;
  5093. }
  5094. hrResult = pFldrEnum->Next(1, &fiFolderInfo, NULL);
  5095. if (FAILED(hrResult))
  5096. {
  5097. TraceResult(hrResult);
  5098. goto exit;
  5099. }
  5100. while (S_OK == hrResult)
  5101. {
  5102. LPSTR pszCurrentChild;
  5103. DWORD cchLengthOfCurrentChild;
  5104. BOOL fResult;
  5105. // Construct path to current child
  5106. cchLengthOfCurrentChild = dwLengthOfCurrentPath +
  5107. lstrlen(fiFolderInfo.pszName) + 1; // HC = 1
  5108. fResult = MemAlloc((void **)&pszCurrentChild, (cchLengthOfCurrentChild + 1) * sizeof(pszCurrentChild[0])); // 1 for null-term
  5109. if (FALSE == fResult)
  5110. {
  5111. m_pStore->FreeRecord(&fiFolderInfo);
  5112. hrResult = TraceResult(E_OUTOFMEMORY);
  5113. goto exit;
  5114. }
  5115. StrCpyN(pszCurrentChild, pszCurrentFldrPath, cchLengthOfCurrentChild+1);
  5116. *(pszCurrentChild + dwLengthOfCurrentPath) = pfiCurrentFldrInfo->bHierarchy;
  5117. StrCatBuff(pszCurrentChild, fiFolderInfo.pszName, cchLengthOfCurrentChild+1);
  5118. // Recurse on the child folder, NEVER suppress folders from here on in
  5119. hrResult = RenameTreeTraversalHelper(wpOperation, pRenameInfo,
  5120. pszCurrentChild, cchLengthOfCurrentChild, TRUE, &fiFolderInfo);
  5121. MemFree(pszCurrentChild);
  5122. if (FAILED(hrResult))
  5123. {
  5124. m_pStore->FreeRecord(&fiFolderInfo);
  5125. TraceResult(hrResult);
  5126. goto exit;
  5127. }
  5128. m_pStore->FreeRecord(&fiFolderInfo);
  5129. if (tidRENAMELIST == wpOperation)
  5130. break; // Special case for LIST: only send ONE list cmd (for first child fldr)
  5131. // Advance the loop
  5132. hrResult = pFldrEnum->Next(1, &fiFolderInfo, NULL);
  5133. if (FAILED(hrResult))
  5134. {
  5135. TraceResult(hrResult);
  5136. goto exit;
  5137. }
  5138. } // while
  5139. exit:
  5140. if (NULL != pFldrEnum)
  5141. pFldrEnum->Release();
  5142. return hrResult;
  5143. } // RenameTreeTraversalHelper
  5144. //***************************************************************************
  5145. //***************************************************************************
  5146. HRESULT CIMAPSync::SubscribeSubtree(FOLDERID idFolder, BOOL fSubscribe)
  5147. {
  5148. HRESULT hrResult;
  5149. IEnumerateFolders *pFldrEnum = NULL;
  5150. FOLDERINFO fiFolderInfo;
  5151. TraceCall("CIMAPSync::SubscribeSubtree");
  5152. IxpAssert(m_cRef > 0);
  5153. IxpAssert(FOLDERID_INVALID != idFolder);
  5154. // First subscribe the current node
  5155. hrResult = m_pStore->SubscribeToFolder(idFolder, fSubscribe, NOSTORECALLBACK);
  5156. if (FAILED(hrResult))
  5157. {
  5158. TraceResult(hrResult);
  5159. goto exit;
  5160. }
  5161. // Now work on the children
  5162. hrResult = m_pStore->EnumChildren(idFolder, fUNSUBSCRIBE, &pFldrEnum);
  5163. if (FAILED(hrResult))
  5164. {
  5165. TraceResult(hrResult);
  5166. goto exit;
  5167. }
  5168. hrResult = pFldrEnum->Next(1, &fiFolderInfo, NULL);
  5169. if (FAILED(hrResult))
  5170. {
  5171. TraceResult(hrResult);
  5172. goto exit;
  5173. }
  5174. while (S_OK == hrResult)
  5175. {
  5176. // Recurse into children
  5177. hrResult = SubscribeSubtree(fiFolderInfo.idFolder, fSubscribe);
  5178. TraceError(hrResult); // Record error but otherwise continue
  5179. // Advance to next child
  5180. m_pStore->FreeRecord(&fiFolderInfo);
  5181. hrResult = pFldrEnum->Next(1, &fiFolderInfo, NULL);
  5182. TraceError(hrResult);
  5183. }
  5184. exit:
  5185. if (NULL != pFldrEnum)
  5186. pFldrEnum->Release();
  5187. return hrResult;
  5188. }
  5189. //***************************************************************************
  5190. // Function: FindRootHierarchyChar
  5191. //
  5192. // Purpose:
  5193. // This function is called to analyze hierarchy character information
  5194. // collected in m_phcfHierarchyCharInfo, and take appropriate action based
  5195. // on the analysis (for example, try to find the hierarchy character using
  5196. // a different method if the current method failed). Currently there are 3
  5197. // methods of finding a hierarchy character. I call these Plan A, B and C.
  5198. // Plan A: Look for hierarchy char in folder hierarchy listing.
  5199. // Plan B: Issue LIST c_szEmpty c_szEmpty
  5200. // Plan C: Create a temp fldr (no HC's in name), list it, delete it
  5201. // Plan Z: Give up and default HC to NIL. This is still under debate.
  5202. //
  5203. // Arguments:
  5204. // BOOL fPlanA_Only [in] - TRUE if this function should execute plan A
  5205. // only, and not execute plans B, C or Z.
  5206. // LPARAM lParam [in] - lParam to use when issuing IMAP commands
  5207. //
  5208. // Returns:
  5209. // If a hierarchy character is found, it is placed in m_cRootHierarchyChar.
  5210. //***************************************************************************
  5211. void CIMAPSync::FindRootHierarchyChar(BOOL fPlanA_Only, LPARAM lParam)
  5212. {
  5213. HRESULT hr;
  5214. TraceCall("CIMAPSync::FindRootHierarchyChar");
  5215. IxpAssert(m_cRef > 0);
  5216. AssertSz(INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar,
  5217. "You want to find the root hierarchy char... but you ALREADY have one. Ah! Efficient.");
  5218. if (NULL == m_phcfHierarchyCharInfo)
  5219. {
  5220. AssertSz(FALSE, "What's the idea, starting a folder DL without a hierarchy char finder?");
  5221. return;
  5222. }
  5223. // Figure out what the hierarchy char is from the collected information
  5224. AnalyzeHierarchyCharInfo();
  5225. // If we haven't found the hierarchy character, launch plan B or C
  5226. if (INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar && FALSE == fPlanA_Only)
  5227. {
  5228. switch (m_phcfHierarchyCharInfo->hcfStage)
  5229. {
  5230. case hcfPLAN_A:
  5231. // Didn't find in folder hierarchy DL (Plan A). Plan "B" is to issue <LIST c_szEmpty c_szEmpty>
  5232. m_phcfHierarchyCharInfo->hcfStage = hcfPLAN_B;
  5233. hr = _EnqueueOperation(tidHIERARCHYCHAR_LIST_B, lParam, icLIST_COMMAND,
  5234. c_szEmpty, uiNORMAL_PRIORITY);
  5235. TraceError(hr);
  5236. break; // case hcfPLAN_A
  5237. case hcfPLAN_B:
  5238. {
  5239. // Didn't find in <LIST c_szEmpty c_szEmpty> (Plan B). Plan "C": attempt CREATE, LIST, DELETE
  5240. // There's no folders on the server, so very little chance of conflict
  5241. // $REVIEW: Localize fldr name when IMAP handles UTF-7. (idsIMAP_HCFTempFldr)
  5242. StrCpyN(m_phcfHierarchyCharInfo->szTempFldrName, "DeleteMe", ARRAYSIZE(m_phcfHierarchyCharInfo->szTempFldrName));
  5243. m_phcfHierarchyCharInfo->hcfStage = hcfPLAN_C;
  5244. hr = _EnqueueOperation(tidHIERARCHYCHAR_CREATE, lParam, icCREATE_COMMAND,
  5245. m_phcfHierarchyCharInfo->szTempFldrName, uiNORMAL_PRIORITY);
  5246. TraceError(hr);
  5247. }
  5248. break; // case hcfPLAN_B
  5249. default:
  5250. case hcfPLAN_C:
  5251. IxpAssert(hcfPLAN_C == m_phcfHierarchyCharInfo->hcfStage);
  5252. AssertSz(FALSE, "This server won't budge - I can't figure out hierarchy char");
  5253. // $REVIEW: Should I put up a message box informing user of situation? Will they understand?
  5254. // We'll just have to assume the hierarchy char is NIL
  5255. // $REVIEW: Is this a good idea? What else can I do about it?
  5256. m_cRootHierarchyChar = '\0';
  5257. break; // case hcfPLAN_C
  5258. }
  5259. }
  5260. // Finally, if we've found hierarchy character, or assumed a value in case
  5261. // hcfPLAN_C above, stop the search and save character to disk
  5262. if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
  5263. {
  5264. StopHierarchyCharSearch();
  5265. hr = LoadSaveRootHierarchyChar(fSAVE_HC);
  5266. TraceError(hr);
  5267. }
  5268. }
  5269. //***************************************************************************
  5270. // Function: AnalyzeHierarchyCharInfo
  5271. //
  5272. // Purpose:
  5273. // This function examines m_phcfHierarchyCharInfo and attempts to determine
  5274. // what the root hierarchy character is. The rules it uses are as follows:
  5275. // 1) If more than 1 Non-NIL, Non-"." (NNND), hierarchy char is indeterminate.
  5276. // 2) If one NNND-HC found, it is taken as HC. "." and NIL HC's are ignored.
  5277. // 3) If no NNND-HC's, but we saw a ".", then "." is HC.
  5278. // 4) If no NNND-HC's, no ".", but we saw a non-INBOX NIL, then NIL is HC.
  5279. //***************************************************************************
  5280. void CIMAPSync::AnalyzeHierarchyCharInfo(void)
  5281. {
  5282. int i;
  5283. int iNonNilNonDotCount;
  5284. BYTE *pbBitArray;
  5285. TraceCall("CIMAPSync::AnalyzeHierarchyCharInfo");
  5286. IxpAssert(m_cRef > 0);
  5287. // First, count the number of non-NIL, non-"." hierarchy chars encountered
  5288. iNonNilNonDotCount = 0;
  5289. pbBitArray = m_phcfHierarchyCharInfo->bHierarchyCharBitArray;
  5290. for (i = 0; i < sizeof(m_phcfHierarchyCharInfo->bHierarchyCharBitArray); i++)
  5291. {
  5292. if (0 != *pbBitArray)
  5293. {
  5294. BYTE bCurrentByte;
  5295. int j;
  5296. // Count the number of bits set in this byte
  5297. bCurrentByte = *pbBitArray;
  5298. IxpAssert(1 == sizeof(bCurrentByte)); // Must change code for > 1 byte at a time
  5299. for (j=0; j<8; j++)
  5300. {
  5301. if (bCurrentByte & 0x01)
  5302. {
  5303. iNonNilNonDotCount += 1;
  5304. m_cRootHierarchyChar = i*8 + j;
  5305. }
  5306. bCurrentByte >>= 1;
  5307. }
  5308. }
  5309. // Advance the pointer
  5310. pbBitArray += 1;
  5311. }
  5312. // Set the hierarchy character based on priority rules: '/' or '\', then '.', then NIL
  5313. if (iNonNilNonDotCount > 1)
  5314. {
  5315. m_cRootHierarchyChar = INVALID_HIERARCHY_CHAR; // Which one WAS it?
  5316. // Nuke all flags and start afresh
  5317. AssertSz(FALSE, "Hey, lookee here! More than one NNND-HC! How quaint.");
  5318. ZeroMemory(m_phcfHierarchyCharInfo->bHierarchyCharBitArray,
  5319. sizeof(m_phcfHierarchyCharInfo->bHierarchyCharBitArray));
  5320. m_phcfHierarchyCharInfo->fDotHierarchyCharSeen = FALSE;
  5321. m_phcfHierarchyCharInfo->fNonInboxNIL_Seen = FALSE;
  5322. }
  5323. else if (0 == iNonNilNonDotCount)
  5324. {
  5325. // Hmmm, looks like we didn't find anything non-'.' or non-NIL
  5326. IxpAssert(INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar); // Just paranoid
  5327. if (m_phcfHierarchyCharInfo->fDotHierarchyCharSeen)
  5328. m_cRootHierarchyChar = '.';
  5329. else if (m_phcfHierarchyCharInfo->fNonInboxNIL_Seen)
  5330. m_cRootHierarchyChar = '\0';
  5331. // If we reach this point and INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar,
  5332. // all flags must be 0, so no need to nuke as for iNonNilNonDotCount > 1 above
  5333. }
  5334. else
  5335. {
  5336. // We found a non-NIL, non-"." hierarchy char. This will take priority
  5337. // over any NIL or "." hierarchy chars we encountered. STILL, I want to
  5338. // know if we talk to a server who has both one NNND-HC and a "." HC.
  5339. IxpAssert(1 == iNonNilNonDotCount);
  5340. AssertSz(FALSE == m_phcfHierarchyCharInfo->fDotHierarchyCharSeen,
  5341. "Take a look at THIS! A server with one NNND-HC and a '.' HC.");
  5342. }
  5343. }
  5344. //***************************************************************************
  5345. // Function: StopHierarchyCharSearch
  5346. //
  5347. // Purpose:
  5348. // This function stops future hierarchy character searches by freeing
  5349. // the m_phcfHierarchyCharInfo struct.
  5350. //***************************************************************************
  5351. void CIMAPSync::StopHierarchyCharSearch(void)
  5352. {
  5353. TraceCall("CIMAPSync::StopHierararchyCharSearch");
  5354. IxpAssert(m_cRef > 0);
  5355. // Deallocate m_phcfHierarchyCharInfo
  5356. if (NULL != m_phcfHierarchyCharInfo)
  5357. {
  5358. delete m_phcfHierarchyCharInfo;
  5359. m_phcfHierarchyCharInfo = NULL;
  5360. }
  5361. else {
  5362. AssertSz(FALSE, "No search for a root-lvl hierarchy character is in progress.");
  5363. }
  5364. }
  5365. //***************************************************************************
  5366. // Function: LoadSaveRootHierarchyChar
  5367. //
  5368. // Arguments:
  5369. // BOOL fSaveHC [in] - TRUE if we should save m_cRootHierarchyChar to
  5370. // the root folder entry in the folder cache. FALSE to read
  5371. // m_cRootHierarchyChar from the root folder entry in foldercache.
  5372. //
  5373. // Returns:
  5374. // HRESULT indicating success or failure.
  5375. //***************************************************************************
  5376. HRESULT CIMAPSync::LoadSaveRootHierarchyChar(BOOL fSaveHC)
  5377. {
  5378. FOLDERINFO fiRootFldrInfo;
  5379. HRESULT hr;
  5380. FOLDERID idCurrFldr;
  5381. BOOL fFreeInfo = FALSE;
  5382. TraceCall("CIMAPSync::LoadSaveRootHierarchyChar");
  5383. IxpAssert(m_cRef > 0);
  5384. IxpAssert(m_pStore != NULL);
  5385. // First thing we have to do is load fiFolderInfo with IMAP server node
  5386. hr = m_pStore->GetFolderInfo(m_idIMAPServer, &fiRootFldrInfo);
  5387. if (FAILED(hr))
  5388. {
  5389. TraceResult(hr);
  5390. goto exit;
  5391. }
  5392. // Now load or save m_cRootHierarchyChar as directed by user
  5393. fFreeInfo = TRUE;
  5394. if (fSaveHC)
  5395. {
  5396. // Save the hierarchy character to disk
  5397. fiRootFldrInfo.bHierarchy = m_cRootHierarchyChar;
  5398. hr = m_pStore->UpdateRecord(&fiRootFldrInfo);
  5399. if (FAILED(hr))
  5400. {
  5401. TraceResult(hr);
  5402. goto exit;
  5403. }
  5404. }
  5405. else
  5406. {
  5407. // Load the hierarchy character
  5408. m_cRootHierarchyChar = fiRootFldrInfo.bHierarchy;
  5409. }
  5410. exit:
  5411. if (fFreeInfo)
  5412. m_pStore->FreeRecord(&fiRootFldrInfo);
  5413. return hr;
  5414. }
  5415. //***************************************************************************
  5416. // Function: CreateNextSpecialFolder
  5417. //
  5418. // Purpose:
  5419. // This function is called after the tidINBOXLIST operation. This function
  5420. // tries to create all IMAP special folders (Sent Items, Drafts, Deleted
  5421. // Items). If no more special folders need to be created, the
  5422. // post-tidINBOXLIST activities are executed (tidPREFIXLIST/tidBROWSESTART/
  5423. // tidFOLDERLIST).
  5424. //
  5425. // Arguments:
  5426. // CREATE_FOLDER_INFO *pcfiCreateInfo [in] - pointer to CREATE_FOLDER_INFO
  5427. // with properly set pcfiCreateInfo. This function will
  5428. // MemFree pcfiCreateInfo->pszFullFolderPath and delete pcfiCreateInfo
  5429. // when all special folders have been created.
  5430. // LPBOOL pfCompletion [out] - returns TRUE if we are done creating special
  5431. // folders.
  5432. //
  5433. // Returns:
  5434. // HRESULT indicating success or failure.
  5435. //***************************************************************************
  5436. HRESULT CIMAPSync::CreateNextSpecialFolder(CREATE_FOLDER_INFO *pcfiCreateInfo,
  5437. LPBOOL pfCompletion)
  5438. {
  5439. HRESULT hr = S_OK;
  5440. HRESULT hrTemp;
  5441. LPARAM lParam = pcfiCreateInfo->lParam;
  5442. char szSpecialFldrPath[2*MAX_PATH + 3]; // Leave room for HC, null-term and asterisk
  5443. BOOL fDone = FALSE;
  5444. BOOL fPostOp = FALSE;
  5445. BOOL fSuppressRelease = FALSE;
  5446. IXPSTATUS ixpCurrentStatus;
  5447. TraceCall("CIMAPSync::CreateNextSpecialFolder");
  5448. IxpAssert(m_cRef > 0);
  5449. IxpAssert(NULL != pcfiCreateInfo);
  5450. IxpAssert(FOLDER_NOTSPECIAL != pcfiCreateInfo->dwCurrentSfType);
  5451. IxpAssert(FOLDER_NOTSPECIAL != pcfiCreateInfo->dwFinalSfType);
  5452. IxpAssert(FOLDER_OUTBOX != pcfiCreateInfo->dwCurrentSfType);
  5453. IxpAssert(FOLDER_OUTBOX != pcfiCreateInfo->dwFinalSfType);
  5454. IxpAssert(pcfiCreateInfo->dwFinalSfType <= FOLDER_MAX);
  5455. IxpAssert(pcfiCreateInfo->dwCurrentSfType <= pcfiCreateInfo->dwFinalSfType);
  5456. szSpecialFldrPath[0] = '\0';
  5457. // If we're looking for root-lvl hierarchy char, maybe this listing will help
  5458. if (NULL != m_phcfHierarchyCharInfo)
  5459. FindRootHierarchyChar(fHCF_PLAN_A_ONLY, lParam);
  5460. hrTemp = LoadSaveRootHierarchyChar(fLOAD_HC);
  5461. TraceError(hrTemp);
  5462. // Get the next folder path if we're in CSF_NEXTFOLDER or CSF_INIT stage
  5463. while (CSF_NEXTFOLDER == pcfiCreateInfo->csfCurrentStage || CSF_INIT == pcfiCreateInfo->csfCurrentStage)
  5464. {
  5465. // If CSF_NEXTFOLDER, bump up current special folder type and check for done-ness
  5466. if (CSF_NEXTFOLDER == pcfiCreateInfo->csfCurrentStage)
  5467. {
  5468. pcfiCreateInfo->dwCurrentSfType += 1;
  5469. if (FOLDER_OUTBOX == pcfiCreateInfo->dwCurrentSfType)
  5470. pcfiCreateInfo->dwCurrentSfType += 1; // Skip Outbox
  5471. if (pcfiCreateInfo->dwCurrentSfType > pcfiCreateInfo->dwFinalSfType)
  5472. {
  5473. fDone = TRUE;
  5474. break;
  5475. }
  5476. }
  5477. hr = ImapUtil_SpecialFldrTypeToPath(m_pszAccountID,
  5478. (SPECIALFOLDER) pcfiCreateInfo->dwCurrentSfType, NULL, m_cRootHierarchyChar,
  5479. szSpecialFldrPath, ARRAYSIZE(szSpecialFldrPath));
  5480. if (SUCCEEDED(hr))
  5481. {
  5482. // Re-use current pcfiCreateInfo to launch next creation attempt
  5483. if (NULL != pcfiCreateInfo->pszFullFolderPath)
  5484. MemFree(pcfiCreateInfo->pszFullFolderPath);
  5485. pcfiCreateInfo->idFolder = FOLDERID_INVALID;
  5486. pcfiCreateInfo->pszFullFolderPath = StringDup(szSpecialFldrPath);
  5487. pcfiCreateInfo->dwFlags = 0;
  5488. pcfiCreateInfo->csfCurrentStage = CSF_LIST;
  5489. break; // We're ready to create some special folders!
  5490. }
  5491. else if (CSF_INIT == pcfiCreateInfo->csfCurrentStage)
  5492. {
  5493. // Need to exit now on ANY failure, to avoid infinite loop
  5494. fDone = TRUE;
  5495. break;
  5496. }
  5497. else if (STORE_E_NOREMOTESPECIALFLDR == hr)
  5498. {
  5499. // Suppress error: current special folder is disabled or not supported on IMAP
  5500. hr = S_OK;
  5501. }
  5502. else
  5503. {
  5504. TraceResult(hr); // Record but ignore unexpected error
  5505. }
  5506. } // while
  5507. // Check for termination condition
  5508. if (fDone)
  5509. goto exit;
  5510. // If we reach this point, we're ready to act on this special folder
  5511. switch (pcfiCreateInfo->csfCurrentStage)
  5512. {
  5513. case CSF_INIT:
  5514. // CSF_INIT should be resolved by loading a special fldr path and going to CSF_LIST!!
  5515. hr = TraceResult(E_UNEXPECTED);
  5516. break;
  5517. case CSF_LIST:
  5518. IxpAssert('\0' != szSpecialFldrPath[0]);
  5519. if (FOLDER_INBOX == pcfiCreateInfo->dwCurrentSfType)
  5520. {
  5521. // For INBOX ONLY: Issue LIST <specialfldr>* to get subchildren of folder (and folder itself)
  5522. StrCatBuff(szSpecialFldrPath, g_szAsterisk, ARRAYSIZE(szSpecialFldrPath)); // Append "*" to special folder name
  5523. }
  5524. pcfiCreateInfo->csfCurrentStage = CSF_LSUBCREATE;
  5525. hr = _EnqueueOperation(tidSPECIALFLDRLIST, (LPARAM) pcfiCreateInfo,
  5526. icLIST_COMMAND, szSpecialFldrPath, uiNORMAL_PRIORITY);
  5527. TraceError(hr);
  5528. break;
  5529. case CSF_LSUBCREATE:
  5530. // Check if the LIST operation returned the special folder path
  5531. if (CFI_RECEIVEDLISTING & pcfiCreateInfo->dwFlags)
  5532. {
  5533. LPSTR pszPath;
  5534. // Folder exists: Issue LSUB <specialfldr>* to get subscribed subchildren
  5535. IxpAssert(NULL != pcfiCreateInfo->pszFullFolderPath &&
  5536. '\0' != pcfiCreateInfo->pszFullFolderPath[0]);
  5537. if (FOLDER_INBOX == pcfiCreateInfo->dwCurrentSfType)
  5538. {
  5539. // For INBOX only: Append "*" to special folder name
  5540. wnsprintf(szSpecialFldrPath, ARRAYSIZE(szSpecialFldrPath), "%s*", pcfiCreateInfo->pszFullFolderPath);
  5541. pszPath = szSpecialFldrPath;
  5542. }
  5543. else
  5544. pszPath = pcfiCreateInfo->pszFullFolderPath;
  5545. pcfiCreateInfo->dwFlags = 0;
  5546. pcfiCreateInfo->csfCurrentStage = CSF_CHECKSUB;
  5547. hr = _EnqueueOperation(tidSPECIALFLDRLSUB, (LPARAM) pcfiCreateInfo,
  5548. icLSUB_COMMAND, pszPath, uiNORMAL_PRIORITY);
  5549. TraceError(hr);
  5550. }
  5551. else
  5552. {
  5553. // Folder does not appear to exist: better create it
  5554. pcfiCreateInfo->dwFlags = 0;
  5555. pcfiCreateInfo->csfCurrentStage = CSF_NEXTFOLDER;
  5556. hr = _EnqueueOperation(tidCREATE, (LPARAM)pcfiCreateInfo, icCREATE_COMMAND,
  5557. pcfiCreateInfo->pszFullFolderPath, uiNORMAL_PRIORITY);
  5558. TraceError(hr);
  5559. }
  5560. break;
  5561. case CSF_CHECKSUB:
  5562. // Check if the LSUB operation returned the special folder path
  5563. if (CFI_RECEIVEDLISTING & pcfiCreateInfo->dwFlags)
  5564. {
  5565. // Special folder is already subscribed, advance to next folder
  5566. IxpAssert(FALSE == fDone);
  5567. pcfiCreateInfo->csfCurrentStage = CSF_NEXTFOLDER;
  5568. hr = CreateNextSpecialFolder(pcfiCreateInfo, &fDone);
  5569. TraceError(hr);
  5570. // BEWARE: do not access pcfiCreateInfo past this point, might be GONE
  5571. fSuppressRelease = TRUE;
  5572. }
  5573. else
  5574. {
  5575. FOLDERID idTemp;
  5576. LPSTR pszLocalPath;
  5577. char szInbox[CCHMAX_STRINGRES];
  5578. SPECIALFOLDER sfType;
  5579. // Special folder not subscribed. Subscribe it!
  5580. // We need to convert full path to local path. Local path = folder name as it appears in our cache
  5581. pszLocalPath = ImapUtil_GetSpecialFolderType(m_pszAccountID,
  5582. pcfiCreateInfo->pszFullFolderPath, m_cRootHierarchyChar,
  5583. m_szRootFolderPrefix, &sfType);
  5584. if (FOLDER_INBOX == sfType)
  5585. {
  5586. // SPECIAL CASE: We need to replace INBOX with the localized name for INBOX
  5587. LoadString(g_hLocRes, idsInbox, szInbox, ARRAYSIZE(szInbox));
  5588. pszLocalPath = szInbox;
  5589. }
  5590. // Remove special folder from unsubscribed folder list (ignore error)
  5591. if (NULL != m_pListHash)
  5592. {
  5593. hr = m_pListHash->Find(pszLocalPath, fREMOVE, (void **) &idTemp);
  5594. IxpAssert(FAILED(hr) || idTemp == pcfiCreateInfo->idFolder);
  5595. }
  5596. // Use full path here (not local path)
  5597. pcfiCreateInfo->csfCurrentStage = CSF_NEXTFOLDER;
  5598. hr = _EnqueueOperation(tidSPECIALFLDRSUBSCRIBE, (LPARAM)pcfiCreateInfo,
  5599. icSUBSCRIBE_COMMAND, pcfiCreateInfo->pszFullFolderPath, uiNORMAL_PRIORITY);
  5600. TraceError(hr);
  5601. }
  5602. break;
  5603. default:
  5604. AssertSz(FALSE, "We are at an unknown stage!");
  5605. hr = TraceResult(E_FAIL);
  5606. break;
  5607. }
  5608. exit:
  5609. // At this point, do not access pcfiCreateInfo if fSuppressRelease is TRUE!!
  5610. if (FAILED(hr))
  5611. fDone = TRUE;
  5612. // Check if we are done and there are post-create operations to perform
  5613. if (FALSE == fSuppressRelease && PCO_NONE != pcfiCreateInfo->pcoNextOp)
  5614. {
  5615. IxpAssert(PCO_APPENDMSG == pcfiCreateInfo->pcoNextOp);
  5616. if (fDone && SUCCEEDED(hr))
  5617. {
  5618. hr = _EnqueueOperation(tidUPLOADMSG, pcfiCreateInfo->lParam, icAPPEND_COMMAND,
  5619. pcfiCreateInfo->pszFullFolderPath, uiNORMAL_PRIORITY);
  5620. TraceError(hr);
  5621. fPostOp = TRUE; // Returns *pfCompletion = FALSE but releases CREATE_FOLDER_INFO
  5622. }
  5623. else if (FAILED(hr))
  5624. {
  5625. APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) pcfiCreateInfo->lParam;
  5626. SafeMemFree(pAppendInfo->pszMsgFlags);
  5627. SafeRelease(pAppendInfo->lpstmMsg);
  5628. delete pAppendInfo;
  5629. }
  5630. }
  5631. if (fDone && FALSE == fSuppressRelease)
  5632. {
  5633. EndFolderList();
  5634. if (NULL != pcfiCreateInfo->pszFullFolderPath)
  5635. MemFree(pcfiCreateInfo->pszFullFolderPath);
  5636. delete pcfiCreateInfo;
  5637. }
  5638. if (NULL != pfCompletion)
  5639. *pfCompletion = (fDone && FALSE == fPostOp);
  5640. return hr;
  5641. }
  5642. // This is not the only place to start a folder list. For example,
  5643. // look at successful tidPREFIXLIST. Use this fn only where applicable.
  5644. HRESULT CIMAPSync::_StartFolderList(LPARAM lParam)
  5645. {
  5646. HRESULT hr = E_FAIL;
  5647. IImnAccount *pAcct;
  5648. TraceCall("CIMAPSync::_StartFolderList");
  5649. IxpAssert(g_pAcctMan);
  5650. IxpAssert(m_cRef > 0);
  5651. if (!g_pAcctMan)
  5652. return E_UNEXPECTED;
  5653. // If user started a folder list, we'll clear the AP_IMAP_DIRTY property
  5654. // The goal is not to pester the user with refresh folder list dialogs
  5655. hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_pszAccountID, &pAcct);
  5656. TraceError(hr);
  5657. if (SUCCEEDED(hr))
  5658. {
  5659. DWORD dwSrc;
  5660. hr = pAcct->GetPropDw(AP_IMAP_DIRTY, &dwSrc);
  5661. TraceError(hr);
  5662. if (SUCCEEDED(hr))
  5663. {
  5664. DWORD dwDest;
  5665. AssertSz(0 == (dwSrc & ~(IMAP_FLDRLIST_DIRTY | IMAP_OE4MIGRATE_DIRTY |
  5666. IMAP_SENTITEMS_DIRTY | IMAP_DRAFTS_DIRTY)), "Please update my dirty bits!");
  5667. // Clear these dirty bits since folder refresh solves all of these problems
  5668. dwDest = dwSrc & ~(IMAP_FLDRLIST_DIRTY | IMAP_OE4MIGRATE_DIRTY |
  5669. IMAP_SENTITEMS_DIRTY | IMAP_DRAFTS_DIRTY);
  5670. if (dwDest != dwSrc)
  5671. {
  5672. hr = pAcct->SetPropDw(AP_IMAP_DIRTY, dwDest);
  5673. TraceError(hr);
  5674. if (SUCCEEDED(hr))
  5675. {
  5676. hr = pAcct->SaveChanges();
  5677. TraceError(hr);
  5678. }
  5679. }
  5680. }
  5681. pAcct->Release();
  5682. }
  5683. // Find out what translation mode we should be in
  5684. hr = SetTranslationMode((FOLDERID) lParam);
  5685. if (FAILED(hr))
  5686. {
  5687. TraceResult(hr);
  5688. goto exit;
  5689. }
  5690. // Did user specify a root folder prefix?
  5691. if ('\0' != m_szRootFolderPrefix[0])
  5692. {
  5693. // User-specified prefix exists. Check if prefix exists on IMAP server
  5694. hr = _EnqueueOperation(tidPREFIXLIST, lParam, icLIST_COMMAND,
  5695. m_szRootFolderPrefix, uiNORMAL_PRIORITY);
  5696. if (FAILED(hr))
  5697. {
  5698. TraceResult(hr);
  5699. goto exit;
  5700. }
  5701. }
  5702. else
  5703. {
  5704. // No root prefix folder, start folder refresh
  5705. hr = _EnqueueOperation(tidFOLDERLIST, lParam, icLIST_COMMAND,
  5706. g_szAsterisk, uiNORMAL_PRIORITY);
  5707. if (FAILED(hr))
  5708. {
  5709. TraceResult(hr);
  5710. goto exit;
  5711. }
  5712. }
  5713. exit:
  5714. return hr;
  5715. }
  5716. //***************************************************************************
  5717. // Function: OnResponse
  5718. // Description: See imnxport.idl (this is part of IIMAPCallback).
  5719. //***************************************************************************
  5720. HRESULT CIMAPSync::OnResponse(const IMAP_RESPONSE *pimr)
  5721. {
  5722. HRESULT hr=S_OK;
  5723. TraceCall("CIMAPSync::OnResponse");
  5724. AssertSingleThreaded;
  5725. switch (pimr->irtResponseType)
  5726. {
  5727. case irtERROR_NOTIFICATION:
  5728. AssertSz(FALSE, "Received IIMAPCallback(irtERROR_NOTIFICATION). Ignoring it.");
  5729. break;
  5730. case irtCOMMAND_COMPLETION:
  5731. hr = _OnCmdComplete(pimr->wParam, pimr->lParam,
  5732. pimr->hrResult, pimr->lpszResponseText);
  5733. break;
  5734. case irtSERVER_ALERT:
  5735. hr = _ShowUserInfo(MAKEINTRESOURCE(idsIMAPServerAlertTitle),
  5736. MAKEINTRESOURCE(idsIMAPServerAlertIntro),
  5737. pimr->lpszResponseText);
  5738. break;
  5739. case irtPARSE_ERROR:
  5740. // Do not display PARSE errors to user. These are really just WARNINGS
  5741. // and so no need to interrupt flow with these. Besides, UW IMAP puts up
  5742. // tonnes of these when you ask for ENVELOPE and it feels that the msgs are mal-formed
  5743. break;
  5744. case irtMAILBOX_UPDATE:
  5745. hr = _OnMailBoxUpdate(pimr->irdResponseData.pmcMsgCount);
  5746. break;
  5747. case irtDELETED_MSG:
  5748. hr = _OnMsgDeleted(pimr->irdResponseData.dwDeletedMsgSeqNum);
  5749. break;
  5750. case irtFETCH_BODY:
  5751. hr = _OnFetchBody(pimr->hrResult, pimr->irdResponseData.pFetchBodyPart);
  5752. break;
  5753. case irtUPDATE_MSG:
  5754. AssertSz(FALSE, "We should no longer get irtUPDATE_MSG, but the extended version instead");
  5755. break;
  5756. case irtUPDATE_MSG_EX:
  5757. hr = _OnUpdateMsg(pimr->wParam, pimr->hrResult, pimr->irdResponseData.pFetchResultsEx);
  5758. break;
  5759. case irtAPPLICABLE_FLAGS:
  5760. hr = _OnApplFlags(pimr->wParam,
  5761. pimr->irdResponseData.imfImapMessageFlags);
  5762. break;
  5763. case irtPERMANENT_FLAGS:
  5764. hr = _OnPermFlags(pimr->wParam,
  5765. pimr->irdResponseData.imfImapMessageFlags,
  5766. pimr->lpszResponseText);
  5767. break;
  5768. case irtUIDVALIDITY:
  5769. hr = _OnUIDValidity(pimr->wParam,
  5770. pimr->irdResponseData.dwUIDValidity,
  5771. pimr->lpszResponseText);
  5772. break;
  5773. case irtREADWRITE_STATUS:
  5774. hr = _OnReadWriteStatus(pimr->wParam,
  5775. pimr->irdResponseData.bReadWrite,
  5776. pimr->lpszResponseText);
  5777. break;
  5778. case irtTRYCREATE:
  5779. _OnTryCreate(pimr->wParam, pimr->lpszResponseText);
  5780. break;
  5781. case irtSEARCH:
  5782. hr = _OnSearchResponse(pimr->wParam,
  5783. pimr->irdResponseData.prlSearchResults);
  5784. break;
  5785. case irtMAILBOX_LISTING:
  5786. hr = _OnMailBoxList(pimr->wParam,
  5787. pimr->lParam,
  5788. pimr->irdResponseData.illrdMailboxListing.pszMailboxName,
  5789. pimr->irdResponseData.illrdMailboxListing.imfMboxFlags,
  5790. pimr->irdResponseData.illrdMailboxListing.cHierarchyChar,
  5791. IXP_S_IMAP_VERBATIM_MBOX == pimr->hrResult);
  5792. break;
  5793. case irtAPPEND_PROGRESS:
  5794. IxpAssert(tidUPLOADMSG == pimr->wParam);
  5795. hr = _OnAppendProgress(pimr->lParam,
  5796. pimr->irdResponseData.papAppendProgress->dwUploaded,
  5797. pimr->irdResponseData.papAppendProgress->dwTotal);
  5798. break;
  5799. case irtMAILBOX_STATUS:
  5800. hr = _OnStatusResponse(pimr->irdResponseData.pisrStatusResponse);
  5801. break;
  5802. default:
  5803. AssertSz(FALSE, "Received unknown IMAP response type via OnResponse");
  5804. break;
  5805. }
  5806. TraceError(hr);
  5807. return S_OK; // never fail the OnResponse
  5808. }
  5809. //***************************************************************************
  5810. // Function: OnTimeout
  5811. // Description: See imnxport.idl for details.
  5812. //***************************************************************************
  5813. HRESULT CIMAPSync::OnTimeout(DWORD *pdwTimeout, IInternetTransport *pTransport)
  5814. {
  5815. HRESULT hr;
  5816. AssertSingleThreaded;
  5817. TraceCall("CIMAPSync::OnTimeout");
  5818. IxpAssert(m_cRef > 0);
  5819. if (NULL == m_pCurrentCB)
  5820. return S_OK; // We'll just wait until the cows come home
  5821. else
  5822. return m_pCurrentCB->OnTimeout(&m_rInetServerInfo, pdwTimeout, IXP_IMAP);
  5823. }
  5824. //***************************************************************************
  5825. // Function: OnLogonPrompt
  5826. // Description: See imnxport.idl for details.
  5827. //***************************************************************************
  5828. HRESULT CIMAPSync::OnLogonPrompt(LPINETSERVER pInetServer,
  5829. IInternetTransport *pTransport)
  5830. {
  5831. BOOL bResult;
  5832. char szPassword[CCHMAX_PASSWORD];
  5833. HRESULT hr;
  5834. HWND hwnd;
  5835. IxpAssert(m_cRef > 0);
  5836. AssertSingleThreaded;
  5837. // Check if we have a cached password that's different from current password
  5838. hr = GetPassword(pInetServer->dwPort, pInetServer->szServerName, pInetServer->szUserName,
  5839. szPassword, ARRAYSIZE(szPassword));
  5840. if (SUCCEEDED(hr) && 0 != lstrcmp(szPassword, pInetServer->szPassword))
  5841. {
  5842. StrCpyN(pInetServer->szPassword, szPassword, ARRAYSIZE(pInetServer->szPassword));
  5843. return S_OK;
  5844. }
  5845. // Propagate call up to callback
  5846. if (NULL == m_pCurrentCB)
  5847. return S_FALSE;
  5848. hr = m_pCurrentCB->OnLogonPrompt(pInetServer, IXP_IMAP);
  5849. if (S_OK == hr)
  5850. {
  5851. // Cache password for future reference this session
  5852. SavePassword(pInetServer->dwPort, pInetServer->szServerName,
  5853. pInetServer->szUserName, pInetServer->szPassword);
  5854. }
  5855. else if (S_FALSE == hr)
  5856. {
  5857. m_hrOperationResult = STORE_E_OPERATION_CANCELED;
  5858. LoadString(g_hLocRes, IDS_IXP_E_USER_CANCEL, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  5859. }
  5860. else if (FAILED(hr))
  5861. m_hrOperationResult = hr;
  5862. return hr;
  5863. }
  5864. //***************************************************************************
  5865. // Function: OnPrompt
  5866. // Description: See imnxport.idl for details.
  5867. //***************************************************************************
  5868. INT CIMAPSync::OnPrompt(HRESULT hrError, LPCTSTR pszText, LPCTSTR pszCaption,
  5869. UINT uType, IInternetTransport *pTransport)
  5870. {
  5871. INT iResult=IDCANCEL;
  5872. IxpAssert(m_cRef > 0);
  5873. AssertSingleThreaded;
  5874. if (NULL != m_pCurrentCB)
  5875. {
  5876. HRESULT hr;
  5877. hr = m_pCurrentCB->OnPrompt(hrError, pszText, pszCaption,
  5878. uType, &iResult);
  5879. TraceError(hr);
  5880. }
  5881. return iResult;
  5882. }
  5883. //***************************************************************************
  5884. // Function: OnStatus
  5885. // Description: See imnxport.idl for details.
  5886. //***************************************************************************
  5887. HRESULT CIMAPSync::OnStatus(IXPSTATUS ixpStatus, IInternetTransport *pTransport)
  5888. {
  5889. HRESULT hrTemp;
  5890. IStoreCallback *pCallback;
  5891. TraceCall("CIMAPSync::OnStatus");
  5892. IxpAssert(m_cRef > 0);
  5893. AssertSingleThreaded;
  5894. if (NULL != m_pCurrentCB)
  5895. pCallback = m_pCurrentCB;
  5896. else
  5897. pCallback = m_pDefCallback;
  5898. // Report status to UI component
  5899. if (NULL != pCallback)
  5900. {
  5901. hrTemp = pCallback->OnProgress(SOT_CONNECTION_STATUS, ixpStatus, 0,
  5902. m_rInetServerInfo.szServerName);
  5903. TraceError(hrTemp);
  5904. }
  5905. switch (ixpStatus)
  5906. {
  5907. case IXP_AUTHORIZED:
  5908. m_issCurrent = issAuthenticated;
  5909. // Clear any OnError's collected (typ. from one or more login rejections)
  5910. m_hrOperationResult = OLE_E_BLANK;
  5911. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_CONNCOMPLETE);
  5912. TraceError(hrTemp);
  5913. break;
  5914. case IXP_DISCONNECTED:
  5915. // If we're disconnecting due to reconnect attempt, do not abort operation
  5916. if (m_fReconnect)
  5917. {
  5918. // if we got disconnected reset the current and pending state
  5919. OnFolderExit();
  5920. m_issCurrent = issNotConnected;
  5921. m_fDisconnecting = FALSE; // We are now done with disconnection
  5922. break;
  5923. }
  5924. // Figure out if we were ever connected
  5925. if (OLE_E_BLANK == m_hrOperationResult)
  5926. {
  5927. if (issNotConnected == m_issCurrent)
  5928. m_hrOperationResult = IXP_E_FAILED_TO_CONNECT;
  5929. else
  5930. m_hrOperationResult = IXP_E_CONNECTION_DROPPED;
  5931. }
  5932. OnFolderExit();
  5933. FlushOperationQueue(issNotConnected, m_hrOperationResult);
  5934. // if we got disconnected reset the current and pending state
  5935. m_issCurrent = issNotConnected;
  5936. m_fDisconnecting = FALSE; // We are now done with disconnection
  5937. // There is only one case where _OnCmdComplete doesn't get the chance
  5938. // to issue the CFSM_EVENT_ERROR, and that's when we never even connect
  5939. if (CFSM_STATE_WAITFORCONN == m_cfsState)
  5940. {
  5941. // Move state machine along to abort this operation and reset
  5942. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_ERROR);
  5943. TraceError(hrTemp);
  5944. m_fTerminating = TRUE; // CFSM_EVENT_ERROR should make us go to CFSM_STATE_OPERATIONCOMPLETE
  5945. }
  5946. break;
  5947. case IXP_CONNECTED:
  5948. // if we get the first 'connected' then we are not yet
  5949. // AUTH'ed, so trasition into issNonAuthenticated if we
  5950. // get authorized, we transition into Authenticated
  5951. if (m_issCurrent == issNotConnected)
  5952. m_issCurrent = issNonAuthenticated;
  5953. break;
  5954. }
  5955. return S_OK; // Yippee, we have status
  5956. }
  5957. //***************************************************************************
  5958. // Function: OnError
  5959. // Description: See imnxport.idl for details.
  5960. //***************************************************************************
  5961. HRESULT CIMAPSync::OnError(IXPSTATUS ixpStatus, LPIXPRESULT pResult,
  5962. IInternetTransport *pTransport)
  5963. {
  5964. AssertSingleThreaded;
  5965. // Currently all OnError calls are due to logon/connection problems
  5966. // Not much we can do: there's no way to show error outside of OnComplete
  5967. // One thing we CAN do is to store error text. If we are disconnected next,
  5968. // we will have something to show the user
  5969. if (NULL != pResult->pszProblem)
  5970. StrCpyN(m_szOperationProblem, pResult->pszProblem, ARRAYSIZE(m_szOperationProblem));
  5971. if (NULL != pResult->pszResponse)
  5972. StrCpyN(m_szOperationDetails, pResult->pszResponse, ARRAYSIZE(m_szOperationDetails));
  5973. m_hrOperationResult = pResult->hrResult;
  5974. // Ignore all errors except for the following:
  5975. if (IXP_E_IMAP_LOGINFAILURE == pResult->hrResult)
  5976. {
  5977. HRESULT hrTemp;
  5978. HWND hwndParent;
  5979. hrTemp = GetParentWindow(0, &hwndParent);
  5980. if (FAILED(hrTemp))
  5981. {
  5982. // Not much we can do here!
  5983. TraceInfoTag(TAG_IMAPSYNC, _MSG("*** CIMAPSync::OnError received for %s operation",
  5984. sotToSz(m_sotCurrent)));
  5985. }
  5986. else
  5987. {
  5988. STOREERROR seErrorInfo;
  5989. // Display error to user ourselves
  5990. FillStoreError(&seErrorInfo, pResult->hrResult, pResult->dwSocketError, NULL, NULL);
  5991. CallbackDisplayError(hwndParent, seErrorInfo.hrResult, &seErrorInfo);
  5992. }
  5993. }
  5994. return S_OK;
  5995. } // OnError
  5996. //***************************************************************************
  5997. // Function: OnCommand
  5998. // Description: See imnxport.idl for details.
  5999. //***************************************************************************
  6000. HRESULT CIMAPSync::OnCommand(CMDTYPE cmdtype, LPSTR pszLine,
  6001. HRESULT hrResponse, IInternetTransport *pTransport)
  6002. {
  6003. IxpAssert(m_cRef > 0);
  6004. AssertSingleThreaded;
  6005. // We should never get this
  6006. AssertSz(FALSE, "*** Received ITransportCallback::OnCommand callback!!!");
  6007. return S_OK;
  6008. }
  6009. //***************************************************************************
  6010. //***************************************************************************
  6011. HRESULT CIMAPSync::GetParentWindow(DWORD dwReserved, HWND *phwndParent)
  6012. {
  6013. HRESULT hr = E_FAIL;
  6014. AssertSingleThreaded;
  6015. // Ask the callback recipient
  6016. if (NULL != m_pCurrentCB)
  6017. {
  6018. hr = m_pCurrentCB->GetParentWindow(dwReserved, phwndParent);
  6019. TraceError(hr);
  6020. }
  6021. else if (NULL != m_pDefCallback)
  6022. {
  6023. hr = m_pDefCallback->GetParentWindow(dwReserved, phwndParent);
  6024. TraceError(hr);
  6025. }
  6026. if (FAILED(hr))
  6027. {
  6028. // We're not supposed to put up any UI
  6029. *phwndParent = NULL;
  6030. }
  6031. return hr;
  6032. }
  6033. //***************************************************************************
  6034. //***************************************************************************
  6035. HRESULT CIMAPSync::GetAccount(LPDWORD pdwServerType, IImnAccount **ppAccount)
  6036. {
  6037. // Locals
  6038. HRESULT hr = E_UNEXPECTED;
  6039. // Invalid Args
  6040. Assert(ppAccount);
  6041. Assert(g_pAcctMan);
  6042. Assert(m_pszAccountID);
  6043. // Initialize
  6044. *ppAccount = NULL;
  6045. if (g_pAcctMan)
  6046. {
  6047. // Find the Account
  6048. IF_FAILEXIT(hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_pszAccountID, ppAccount));
  6049. // Set the server type
  6050. *pdwServerType = SRV_IMAP;
  6051. }
  6052. exit:
  6053. // Done
  6054. return(hr);
  6055. }
  6056. //***************************************************************************
  6057. //***************************************************************************
  6058. HRESULT CIMAPSync::_ShowUserInfo(LPSTR pszTitle, LPSTR pszText1, LPSTR pszText2)
  6059. {
  6060. char szTitle[CCHMAX_STRINGRES];
  6061. char szUserInfo[2 * CCHMAX_STRINGRES];
  6062. LPSTR p;
  6063. HRESULT hr;
  6064. INT iTemp;
  6065. IStoreCallback *pCallback;
  6066. TraceCall("CIMAPSync::_ShowUserInfo");
  6067. AssertSingleThreaded;
  6068. // Check args
  6069. if (NULL == pszTitle || NULL == pszText1)
  6070. {
  6071. AssertSz(FALSE, "pszTitle and pszText1 cannot be NULL");
  6072. hr = TraceResult(E_INVALIDARG);
  6073. goto exit;
  6074. }
  6075. if (NULL != m_pCurrentCB)
  6076. pCallback = m_pCurrentCB;
  6077. else
  6078. pCallback = m_pDefCallback;
  6079. // Check if we have a callback to call
  6080. if (NULL == pCallback)
  6081. return S_OK; // Nothing to do here!
  6082. if (IS_INTRESOURCE(pszTitle))
  6083. {
  6084. LoadString(g_hLocRes, PtrToUlong(pszTitle), szTitle, ARRAYSIZE(szTitle));
  6085. pszTitle = szTitle;
  6086. }
  6087. p = szUserInfo;
  6088. if (IS_INTRESOURCE(pszText1))
  6089. p += LoadString(g_hLocRes, PtrToUlong(pszText1), szUserInfo, ARRAYSIZE(szUserInfo));
  6090. if (NULL != pszText2)
  6091. {
  6092. if (IS_INTRESOURCE(pszText2))
  6093. LoadString(g_hLocRes, PtrToUlong(pszText2), p, ARRAYSIZE(szUserInfo) -
  6094. (int) (p - szUserInfo));
  6095. else
  6096. StrCpyN(p, pszText2, ARRAYSIZE(szUserInfo) - (int) (p - szUserInfo));
  6097. }
  6098. hr = pCallback->OnPrompt(S_OK, szUserInfo, pszTitle, MB_OK, &iTemp);
  6099. TraceError(hr);
  6100. exit:
  6101. return hr;
  6102. }
  6103. //***************************************************************************
  6104. // Function: OnMailBoxUpdate
  6105. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6106. //***************************************************************************
  6107. HRESULT CIMAPSync::_OnMailBoxUpdate(MBOX_MSGCOUNT *pNewMsgCount)
  6108. {
  6109. HRESULT hrTemp;
  6110. TraceCall("CIMAPSync::OnMailBoxUpdate");
  6111. IxpAssert(m_cRef > 0);
  6112. IxpAssert(NULL != pNewMsgCount);
  6113. // Handle EXISTS response - calculate number of new msgs, update m_dwMsgCount
  6114. if (pNewMsgCount->bGotExistsResponse)
  6115. {
  6116. // Since we are guaranteed to get all EXPUNGE responses, and since
  6117. // we decrement m_dwMsgCount for each EXPUNGE, number of new messages
  6118. // is difference between current EXISTS count and m_dwMsgCount.
  6119. if (m_fMsgCountValid)
  6120. {
  6121. if (pNewMsgCount->dwExists >= m_dwMsgCount)
  6122. m_dwNumNewMsgs += pNewMsgCount->dwExists - m_dwMsgCount;
  6123. }
  6124. m_dwMsgCount = pNewMsgCount->dwExists;
  6125. m_fMsgCountValid = TRUE;
  6126. // Make sure msg seq num <-> UID table is proper size for this mbox
  6127. // Record but otherwise ignore errors
  6128. hrTemp = m_pTransport->ResizeMsgSeqNumTable(pNewMsgCount->dwExists);
  6129. TraceError(hrTemp);
  6130. }
  6131. // New messages! Woo hoo!
  6132. if (m_dwNumNewMsgs > 0)
  6133. {
  6134. m_dwSyncToDo |= (m_dwSyncFolderFlags & SYNC_FOLDER_NEW_HEADERS);
  6135. hrTemp = _SyncHeader();
  6136. TraceError(hrTemp);
  6137. }
  6138. return S_OK;
  6139. }
  6140. //***************************************************************************
  6141. // Function: _OnMsgDeleted
  6142. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6143. //***************************************************************************
  6144. HRESULT CIMAPSync::_OnMsgDeleted(DWORD dwDeletedMsgSeqNum)
  6145. {
  6146. DWORD dwDeletedMsgUID, dwHighestMSN;
  6147. HRESULT hr;
  6148. MESSAGEIDLIST midList;
  6149. MESSAGEID mid;
  6150. TraceCall("CIMAPSync::DeletedMsgNotification");
  6151. IxpAssert(m_cRef > 0);
  6152. IxpAssert(0 != dwDeletedMsgSeqNum);
  6153. // Regardless of outcome, an EXPUNGE means there's one less msg - update vars
  6154. if (m_fMsgCountValid)
  6155. m_dwMsgCount -= 1;
  6156. // Is this msg seq num within our translation range?
  6157. hr = m_pTransport->GetHighestMsgSeqNum(&dwHighestMSN);
  6158. if (SUCCEEDED(hr) && dwDeletedMsgSeqNum > dwHighestMSN)
  6159. return S_OK; // We got an EXPUNGE for a hdr we've never DL'ed
  6160. // Find out who got the axe
  6161. hr = m_pTransport->MsgSeqNumToUID(dwDeletedMsgSeqNum, &dwDeletedMsgUID);
  6162. if (FAILED(hr) || 0 == dwDeletedMsgUID)
  6163. {
  6164. // Failure here means we either got a bogus msg seq num, or we got an
  6165. // EXPUNGE during SELECT (before the tidFETCH_CACHED_FLAGS transaction).
  6166. // If the latter is true, it's no big deal since FETCHes will sync us up.
  6167. TraceResult(E_FAIL); // Record but otherwise ignore error
  6168. goto exit;
  6169. }
  6170. // Delete message from the cache. Note that we do not care about error
  6171. // because even in case of error, we must resequence the table
  6172. mid = (MESSAGEID)((DWORD_PTR)dwDeletedMsgUID);
  6173. midList.cAllocated = 0;
  6174. midList.cMsgs = 1;
  6175. midList.prgidMsg = &mid;
  6176. hr = m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, &midList, NULL, NULL);
  6177. TraceError(hr);
  6178. exit:
  6179. // Resequence our msg seq num <-> UID table
  6180. hr = m_pTransport->RemoveSequenceNum(dwDeletedMsgSeqNum);
  6181. TraceError(hr);
  6182. return S_OK;
  6183. }
  6184. //***************************************************************************
  6185. // Function: _OnFetchBody
  6186. // Purpose: This function handles the irtFETCH_BODY response type of
  6187. // IIMAPCallback::OnResponse.
  6188. //***************************************************************************
  6189. HRESULT CIMAPSync::_OnFetchBody(HRESULT hrFetchBodyResult,
  6190. FETCH_BODY_PART *pFetchBodyPart)
  6191. {
  6192. LPSTREAM lpstmRFC822; // Only used for RFC822.HEADER
  6193. HRESULT hr;
  6194. TraceCall("CIMAPSync::_OnFetchBody");
  6195. IxpAssert(m_cRef > 0);
  6196. IxpAssert(NULL != pFetchBodyPart);
  6197. IxpAssert(NULL != pFetchBodyPart->pszBodyTag);
  6198. IxpAssert(NULL != pFetchBodyPart->pszData);
  6199. IxpAssert(0 != pFetchBodyPart->dwMsgSeqNum);
  6200. // Initialize variables
  6201. hr = S_OK;
  6202. lpstmRFC822 = (LPSTREAM) pFetchBodyPart->lpFetchCookie2;
  6203. // Check for (and deal with) failure
  6204. if (FAILED(hrFetchBodyResult))
  6205. {
  6206. DWORD dwUID;
  6207. TraceResult(hrFetchBodyResult);
  6208. pFetchBodyPart->lpFetchCookie1 = fbpNONE;
  6209. if (NULL != lpstmRFC822)
  6210. {
  6211. lpstmRFC822->Release();
  6212. pFetchBodyPart->lpFetchCookie2 = NULL;
  6213. }
  6214. // Get the UID of this message
  6215. hr = m_pTransport->MsgSeqNumToUID(pFetchBodyPart->dwMsgSeqNum, &dwUID);
  6216. if (FAILED(hr))
  6217. {
  6218. TraceResult(hr);
  6219. goto exit;
  6220. }
  6221. NotifyMsgRecipients(dwUID, fCOMPLETED, NULL, hrFetchBodyResult, NULL);
  6222. goto exit;
  6223. }
  6224. // Identify this fetch body tag, if we haven't already
  6225. if (fbpNONE == pFetchBodyPart->lpFetchCookie1)
  6226. {
  6227. // First check for incoming body, then check for incoming header
  6228. if (0 == lstrcmpi(pFetchBodyPart->pszBodyTag, "RFC822") ||
  6229. 0 == lstrcmpi(pFetchBodyPart->pszBodyTag, "BODY[]"))
  6230. {
  6231. pFetchBodyPart->lpFetchCookie1 = fbpBODY;
  6232. }
  6233. else if (0 == lstrcmpi(pFetchBodyPart->pszBodyTag, "RFC822.HEADER") ||
  6234. 0 == lstrcmpi(pFetchBodyPart->pszBodyTag, "BODY[HEADER.FIELDS"))
  6235. {
  6236. pFetchBodyPart->lpFetchCookie1 = fbpHEADER;
  6237. // Create a stream
  6238. IxpAssert(NULL == lpstmRFC822);
  6239. hr = MimeOleCreateVirtualStream(&lpstmRFC822);
  6240. if (FAILED(hr))
  6241. {
  6242. TraceResult(hr);
  6243. goto exit;
  6244. }
  6245. pFetchBodyPart->lpFetchCookie2 = (LPARAM) lpstmRFC822;
  6246. }
  6247. else
  6248. {
  6249. AssertSz(FALSE, "What kind of tag is this?");
  6250. pFetchBodyPart->lpFetchCookie1 = fbpUNKNOWN;
  6251. }
  6252. }
  6253. // If this is a message body, update progress
  6254. if (fbpBODY == pFetchBodyPart->lpFetchCookie1)
  6255. {
  6256. DWORD dwUID;
  6257. hr = m_pTransport->MsgSeqNumToUID(pFetchBodyPart->dwMsgSeqNum, &dwUID);
  6258. if (FAILED(hr))
  6259. {
  6260. TraceResult(hr);
  6261. goto exit;
  6262. }
  6263. NotifyMsgRecipients(dwUID, fPROGRESS, pFetchBodyPart, S_OK, NULL);
  6264. }
  6265. // Append the data to a stream
  6266. if (NULL != lpstmRFC822)
  6267. {
  6268. DWORD dwNumBytesWritten;
  6269. IxpAssert(fbpHEADER == pFetchBodyPart->lpFetchCookie1);
  6270. hr = lpstmRFC822->Write(pFetchBodyPart->pszData,
  6271. pFetchBodyPart->dwSizeOfData, &dwNumBytesWritten);
  6272. if (FAILED(hr))
  6273. {
  6274. TraceResult(hr);
  6275. goto exit;
  6276. }
  6277. IxpAssert(dwNumBytesWritten == pFetchBodyPart->dwSizeOfData);
  6278. }
  6279. exit:
  6280. return S_OK;
  6281. }
  6282. //***************************************************************************
  6283. // Function: _OnUpdateMsg
  6284. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6285. //***************************************************************************
  6286. HRESULT CIMAPSync::_OnUpdateMsg(WPARAM tid, HRESULT hrFetchCmdResult,
  6287. FETCH_CMD_RESULTS_EX *pFetchResults)
  6288. {
  6289. HRESULT hrTemp;
  6290. TraceCall("CIMAPSync::UpdateMsgNotification");
  6291. IxpAssert(m_cRef > 0);
  6292. IxpAssert(NULL != pFetchResults);
  6293. // Keep our msg seq num <-> UID table up to date
  6294. if (pFetchResults->bUID)
  6295. {
  6296. // Record error but otherwise ignore
  6297. hrTemp = m_pTransport->UpdateSeqNumToUID(pFetchResults->dwMsgSeqNum,
  6298. pFetchResults->dwUID);
  6299. TraceError(hrTemp);
  6300. }
  6301. else
  6302. {
  6303. HRESULT hr;
  6304. DWORD dwHighestMSN;
  6305. // No UID w/ FETCH resp means this is unsolicited: check that we already have hdr
  6306. hr = m_pTransport->GetHighestMsgSeqNum(&dwHighestMSN);
  6307. TraceError(hr);
  6308. if (SUCCEEDED(hr) && pFetchResults->dwMsgSeqNum > dwHighestMSN)
  6309. goto exit; // Can't translate MsgSeqNum to UID, typ. because svr is reporting
  6310. // flag updates on msgs which we haven't had hdrs DL'ed yet. No prob,
  6311. // if svr reported EXISTS correctly, we should be DL'ing hdrs shortly
  6312. // Either unsolicited FETCH, or server needs to learn to send UID for UID cmds
  6313. hr = m_pTransport->MsgSeqNumToUID(pFetchResults->dwMsgSeqNum, &pFetchResults->dwUID);
  6314. if (FAILED(hr) || 0 == pFetchResults->dwUID)
  6315. {
  6316. TraceResult(hr);
  6317. goto exit;
  6318. }
  6319. else
  6320. pFetchResults->bUID = TRUE;
  6321. }
  6322. // We classify our fetch responses as header downloads, body downloads,
  6323. // and flag updates.
  6324. if (pFetchResults->bEnvelope)
  6325. {
  6326. // We only get envelopes when we are asking for headers
  6327. Assert(fbpBODY != pFetchResults->lpFetchCookie1);
  6328. pFetchResults->lpFetchCookie1 = fbpHEADER;
  6329. }
  6330. switch (pFetchResults->lpFetchCookie1)
  6331. {
  6332. case fbpHEADER:
  6333. UpdateMsgHeader(tid, hrFetchCmdResult, pFetchResults);
  6334. break;
  6335. case fbpBODY:
  6336. UpdateMsgBody(tid, hrFetchCmdResult, pFetchResults);
  6337. break;
  6338. default:
  6339. AssertSz(fbpNONE == pFetchResults->lpFetchCookie1, "Unhandled FetchBodyPart type");
  6340. UpdateMsgFlags(tid, hrFetchCmdResult, pFetchResults);
  6341. break;
  6342. }
  6343. exit:
  6344. // If we allocated a stream, free it
  6345. if (NULL != pFetchResults->lpFetchCookie2)
  6346. ((LPSTREAM)pFetchResults->lpFetchCookie2)->Release();
  6347. return S_OK;
  6348. }
  6349. //***************************************************************************
  6350. // Function: UpdateMsgHeader
  6351. //
  6352. // Purpose:
  6353. // This function takes a message header returned via FETCH response,
  6354. // caches it, and notifies the view.
  6355. //
  6356. // Arguments:
  6357. // WPARAM wpTransactionID [in] - transaction ID of fetch response.
  6358. // Currently ignored.
  6359. // HRESULT hrFetchCmdResult [in] - success/failure of FETCH cmd
  6360. // const FETCH_CMD_RESULTS_EX *pFetchResults [in] - the information from
  6361. // the FETCH response.
  6362. //***************************************************************************
  6363. HRESULT CIMAPSync::UpdateMsgHeader( WPARAM tid,
  6364. HRESULT hrFetchCmdResult,
  6365. FETCH_CMD_RESULTS_EX *pFetchResults)
  6366. {
  6367. HRESULT hr;
  6368. MESSAGEINFO miMsgInfo={0};
  6369. TraceCall("CIMAPSync::UpdateMsgHeader");
  6370. IxpAssert(m_cRef > 0);
  6371. IxpAssert(NULL != pFetchResults);
  6372. IxpAssert(pFetchResults->bUID);
  6373. IxpAssert(fbpHEADER == pFetchResults->lpFetchCookie1);
  6374. // Make sure we have everything we need
  6375. if (FAILED(hrFetchCmdResult))
  6376. {
  6377. // Error on FETCH response, forget this header
  6378. hr = TraceResult(hrFetchCmdResult);
  6379. goto exit;
  6380. }
  6381. if (NULL == pFetchResults->lpFetchCookie2 && FALSE == pFetchResults->bEnvelope)
  6382. {
  6383. // I don't do ANYTHING without an RFC822.HEADER stream or envelope
  6384. hr = TraceResult(E_INVALIDARG);
  6385. goto exit;
  6386. }
  6387. // First, check if we already have this header cached
  6388. miMsgInfo.idMessage = (MESSAGEID)((DWORD_PTR)pFetchResults->dwUID);
  6389. hr = m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &miMsgInfo, NULL);
  6390. if (DB_S_FOUND == hr)
  6391. {
  6392. // No need for alarm, we'll just swallow this header
  6393. m_pFolder->FreeRecord(&miMsgInfo);
  6394. goto exit; // We already have this header - we shouldn't have gotten this
  6395. // On some IMAP servers, if you UID FETCH <highestCachedUID + 1>:*
  6396. // you get a fetch response for highestCachedUID! Ignore this fetch result.
  6397. }
  6398. m_pFolder->FreeRecord(&miMsgInfo);
  6399. // Cache this header, since it's not in our cache already
  6400. hr = Fill_MESSAGEINFO(pFetchResults, &miMsgInfo);
  6401. if (FAILED(hr))
  6402. {
  6403. FreeMessageInfo(&miMsgInfo); // There could be a couple of fields in there
  6404. TraceResult(hr);
  6405. goto exit;
  6406. }
  6407. hr = m_pFolder->InsertRecord(&miMsgInfo);
  6408. if (SUCCEEDED(hr) && 0 == (ARF_READ & miMsgInfo.dwFlags))
  6409. m_fNewMail = TRUE;
  6410. FreeMessageInfo(&miMsgInfo);
  6411. if (FAILED(hr))
  6412. {
  6413. TraceResult(hr);
  6414. goto exit;
  6415. }
  6416. // Bump up synchronize headers progress
  6417. // Currently the only way we can get a hdr is through sync-up. Later on we'll
  6418. // be able to get individual hdrs at which point this code should be updated
  6419. if (TRUE)
  6420. {
  6421. DWORD dwNumExpectedMsgs;
  6422. // Recalculate total number of expected messages
  6423. if (m_fMsgCountValid &&
  6424. m_dwMsgCount + m_dwNumHdrsDLed + 1 >= pFetchResults->dwMsgSeqNum)
  6425. {
  6426. IxpAssert(m_dwNumHdrsDLed < pFetchResults->dwMsgSeqNum);
  6427. dwNumExpectedMsgs = m_dwMsgCount + m_dwNumHdrsDLed + 1 -
  6428. pFetchResults->dwMsgSeqNum;
  6429. if (dwNumExpectedMsgs != m_dwNumHdrsToDL)
  6430. {
  6431. // Record but otherwise ignore this fact
  6432. TraceInfoTag(TAG_IMAPSYNC, _MSG("*** dwNumExpectedMsgs = %lu, m_dwNumHdrsToDL = %lu!",
  6433. dwNumExpectedMsgs, m_dwNumHdrsToDL));
  6434. }
  6435. }
  6436. m_dwNumHdrsDLed += 1;
  6437. if (pFetchResults->bMsgFlags && ISFLAGCLEAR(pFetchResults->mfMsgFlags, IMAP_MSG_SEEN))
  6438. m_dwNumUnreadDLed += 1;
  6439. if (NULL != m_pCurrentCB && SOT_SYNC_FOLDER == m_sotCurrent)
  6440. {
  6441. HRESULT hrTemp;
  6442. hrTemp = m_pCurrentCB->OnProgress(SOT_SYNC_FOLDER,
  6443. m_dwNumHdrsDLed, dwNumExpectedMsgs, m_pszFldrLeafName);
  6444. TraceError(hrTemp);
  6445. }
  6446. }
  6447. exit:
  6448. return hr;
  6449. }
  6450. //***************************************************************************
  6451. // Function: UpdateMsgBody
  6452. //
  6453. // Purpose:
  6454. // This function takes a message body returned via FETCH response,
  6455. // caches it, and notifies all interested parties (there may be more
  6456. // than one).
  6457. //
  6458. // Arguments:
  6459. // WPARAM wpTransactionID [in] - transaction ID of fetch response.
  6460. // Currently ignored.
  6461. // HRESULT hrFetchCmdResult [in] - success/failure of FETCH cmd
  6462. // const FETCH_CMD_RESULTS_EX *pFetchResults [in] - the information from
  6463. // the FETCH response.
  6464. //***************************************************************************
  6465. HRESULT CIMAPSync::UpdateMsgBody( WPARAM tid,
  6466. HRESULT hrFetchCmdResult,
  6467. FETCH_CMD_RESULTS_EX *pFetchResults)
  6468. {
  6469. TraceCall("CIMAPSync::UpdateMsgBody");
  6470. IxpAssert(m_cRef > 0);
  6471. IxpAssert(NULL != pFetchResults);
  6472. IxpAssert(pFetchResults->bUID);
  6473. IxpAssert(fbpBODY == pFetchResults->lpFetchCookie1);
  6474. // Record any fetch error
  6475. TraceError(hrFetchCmdResult);
  6476. // We used to call NotifyMsgRecipients(fCOMPLETED) here, but since we only
  6477. // get one body at a time, we should defer to _OnCmdComplete. This is because
  6478. // FETCH body responses have multiple failure modes: tagged OK with no body,
  6479. // tagged NO with no body, tagged OK with literal of size 0 (Netscape). To
  6480. // easily avoid calling NotifyMsgRecipients twice, don't call from here.
  6481. // It's possible to have FETCH response with no body: If you fetch an expunged
  6482. // msg from a Netscape svr, you get a literal of size 0. Check for this case.
  6483. if (SUCCEEDED(hrFetchCmdResult) && FALSE == m_fGotBody)
  6484. hrFetchCmdResult = STORE_E_EXPIRED;
  6485. if (FAILED(hrFetchCmdResult) &&
  6486. (SUCCEEDED(m_hrOperationResult) || OLE_E_BLANK == m_hrOperationResult))
  6487. {
  6488. // We don't have an error set yet. Record this error
  6489. m_hrOperationResult = hrFetchCmdResult;
  6490. }
  6491. return S_OK;
  6492. }
  6493. //***************************************************************************
  6494. // Function: UpdateMsgFlags
  6495. //
  6496. // Purpose:
  6497. // This function takes a message's flags returned via FETCH response,
  6498. // updates the cache, and notifies the view.
  6499. //
  6500. // Arguments:
  6501. // WPARAM wpTransactionID [in] - transaction ID of fetch response.
  6502. // Currently ignored.
  6503. // HRESULT hrFetchCmdResult [in] - success/failure of FETCH cmd
  6504. // const FETCH_CMD_RESULTS_EX *pFetchResults [in] - the information from
  6505. // the FETCH response.
  6506. //***************************************************************************
  6507. HRESULT CIMAPSync::UpdateMsgFlags( WPARAM tid,
  6508. HRESULT hrFetchCmdResult,
  6509. FETCH_CMD_RESULTS_EX *pFetchResults)
  6510. {
  6511. HRESULT hr = S_OK;
  6512. MESSAGEINFO miMsgInfo;
  6513. MESSAGEFLAGS mfFlags;
  6514. BOOL fFreeMsgInfo = FALSE;
  6515. TraceCall("CIMAPSync::UpdateMsgFlags");
  6516. IxpAssert(m_cRef > 0);
  6517. IxpAssert(NULL != pFetchResults);
  6518. IxpAssert(pFetchResults->bUID);
  6519. IxpAssert(fbpNONE == pFetchResults->lpFetchCookie1);
  6520. IxpAssert(0 == pFetchResults->lpFetchCookie2);
  6521. if (FAILED(hrFetchCmdResult))
  6522. {
  6523. // Error on FETCH response, forget this flag update
  6524. hr = TraceResult(hrFetchCmdResult);
  6525. goto exit;
  6526. }
  6527. // We expect that if there is no header and no body, that this is either
  6528. // a solicited or unsolicited flag update
  6529. if (FALSE == pFetchResults->bMsgFlags)
  6530. {
  6531. hr = S_OK; // We'll just ignore this fetch response. No need to wig out
  6532. goto exit;
  6533. }
  6534. // Get the header for this message
  6535. miMsgInfo.idMessage = (MESSAGEID)((DWORD_PTR)pFetchResults->dwUID);
  6536. hr = m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &miMsgInfo, NULL);
  6537. if (DB_S_FOUND != hr)
  6538. {
  6539. TraceError(hr);
  6540. goto exit;
  6541. }
  6542. // Convert IMAP flags to ARF_* flags
  6543. fFreeMsgInfo = TRUE;
  6544. mfFlags = miMsgInfo.dwFlags;
  6545. mfFlags &= ~DwConvertIMAPtoARF(IMAP_MSG_ALLFLAGS); // Clear old IMAP flags
  6546. mfFlags |= DwConvertIMAPtoARF(pFetchResults->mfMsgFlags); // Set IMAP flags
  6547. // Are the new flags any different from our cached ones?
  6548. if (mfFlags != miMsgInfo.dwFlags)
  6549. {
  6550. // Save new flags
  6551. miMsgInfo.dwFlags = mfFlags;
  6552. hr = m_pFolder->UpdateRecord(&miMsgInfo);
  6553. if (FAILED(hr))
  6554. {
  6555. TraceResult(hr);
  6556. goto exit;
  6557. }
  6558. }
  6559. exit:
  6560. if (fFreeMsgInfo)
  6561. m_pFolder->FreeRecord(&miMsgInfo);
  6562. return hr;
  6563. }
  6564. //***************************************************************************
  6565. // Function: _OnApplFlags
  6566. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6567. //***************************************************************************
  6568. HRESULT CIMAPSync::_OnApplFlags(WPARAM tid, IMAP_MSGFLAGS imfApplicableFlags)
  6569. {
  6570. TraceCall("CIMAPSync::_OnApplFlags");
  6571. // Save flags and process after SELECT is complete. DO NOT PROCESS HERE
  6572. // because this response can be part of previously selected folder.
  6573. return S_OK;
  6574. }
  6575. //***************************************************************************
  6576. // Function: _OnPermFlags
  6577. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6578. //***************************************************************************
  6579. HRESULT CIMAPSync::_OnPermFlags(WPARAM tid, IMAP_MSGFLAGS imfApplicableFlags, LPSTR lpszResponseText)
  6580. {
  6581. TraceCall("CIMAPSync::PermanentFlagsNotification");
  6582. IxpAssert(m_cRef > 0);
  6583. // Save flags and process after SELECT is complete. DO NOT PROCESS HERE
  6584. // because this response can be part of previously selected folder.
  6585. return S_OK;
  6586. }
  6587. //***************************************************************************
  6588. // Function: _OnUIDValidity
  6589. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6590. //***************************************************************************
  6591. HRESULT CIMAPSync::_OnUIDValidity(WPARAM tid, DWORD dwUIDValidity, LPSTR lpszResponseText)
  6592. {
  6593. TraceCall("CIMAPSync::UIDValidityNotification");
  6594. IxpAssert(m_cRef > 0);
  6595. IxpAssert(NULL != lpszResponseText);
  6596. // Save UIDVALIDITY and process after SELECT is complete. DO NOT PROCESS HERE
  6597. // because this response can be part of previously selected folder.
  6598. m_dwUIDValidity = dwUIDValidity;
  6599. return S_OK;
  6600. }
  6601. //***************************************************************************
  6602. // Function: _OnReadWriteStatus
  6603. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6604. //***************************************************************************
  6605. HRESULT CIMAPSync::_OnReadWriteStatus(WPARAM tid, BOOL bReadWrite, LPSTR lpszResponseText)
  6606. {
  6607. TraceCall("CIMAPSync::_OnReadWriteStatus");
  6608. IxpAssert(NULL != lpszResponseText);
  6609. // Save status and process after SELECT is complete. DO NOT PROCESS HERE
  6610. // because this response can be part of previously selected folder.
  6611. // I'm ignoring above statement because UW server sends READ-ONLY unilaterally.
  6612. // Above statement isn't currently valid anyway, I don't re-use connection if
  6613. // it's in the middle of SELECT (found out how bad it was after I tried it).
  6614. // Look for READ-WRITE to READ-ONLY transition
  6615. if (rwsUNINITIALIZED != m_rwsReadWriteStatus)
  6616. {
  6617. if (rwsREAD_WRITE == m_rwsReadWriteStatus && FALSE == bReadWrite)
  6618. {
  6619. HRESULT hrTemp;
  6620. hrTemp = _ShowUserInfo(MAKEINTRESOURCE(idsAthenaMail),
  6621. MAKEINTRESOURCE(idsIMAPFolderReadOnly), lpszResponseText);
  6622. TraceError(hrTemp);
  6623. }
  6624. }
  6625. // Save current read-write status for future reference
  6626. if (bReadWrite)
  6627. m_rwsReadWriteStatus = rwsREAD_WRITE;
  6628. else
  6629. m_rwsReadWriteStatus = rwsREAD_ONLY;
  6630. return S_OK;
  6631. }
  6632. //***************************************************************************
  6633. // Function: TryCreateNotification
  6634. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6635. //***************************************************************************
  6636. HRESULT CIMAPSync::_OnTryCreate(WPARAM tid, LPSTR lpszResponseText)
  6637. {
  6638. TraceCall("CIMAPSync::TryCreateNotification");
  6639. IxpAssert(m_cRef > 0);
  6640. IxpAssert(NULL != lpszResponseText);
  6641. // Save response and process after SELECT is complete. DO NOT PROCESS HERE
  6642. // because this response can be part of previously selected folder.
  6643. return S_OK;
  6644. }
  6645. //***************************************************************************
  6646. // Function: SearchResponseNotification
  6647. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6648. //***************************************************************************
  6649. HRESULT CIMAPSync::_OnSearchResponse(WPARAM tid, IRangeList *prlSearchResults)
  6650. {
  6651. TraceCall("CIMAPSync::SearchResponseNotification");
  6652. IxpAssert(m_cRef > 0);
  6653. IxpAssert(NULL != prlSearchResults);
  6654. // Process search response here (currently does nothing)
  6655. return S_OK;
  6656. }
  6657. //***************************************************************************
  6658. // Function: OnCmdComplete
  6659. // Description: See imnxport.idl (this is part of IIMAPCallback).
  6660. //
  6661. // For a CIMAPFolder class to be useful, it must enter the SELECTED state
  6662. // on the IMAP server. This function is written so that entering the SELECTED
  6663. // state is done in an orderly fashion: First the login, then the SELECT
  6664. // command.
  6665. //
  6666. // Once we are in SELECTED state, we can send IMAP commands at any time.
  6667. //***************************************************************************
  6668. HRESULT CIMAPSync::_OnCmdComplete(WPARAM tid, LPARAM lParam, HRESULT hrCompletionResult,
  6669. LPCSTR lpszResponseText)
  6670. {
  6671. TCHAR szFmt[CCHMAX_STRINGRES];
  6672. IStoreCallback *pCallback = NULL;
  6673. STOREOPERATIONTYPE sotOpType = SOT_INVALID;
  6674. BOOL fCompletion = FALSE;
  6675. BOOL fCreateDone = FALSE,
  6676. fDelFldrFromCache = FALSE,
  6677. fSuppressDetails = FALSE;
  6678. HRESULT hrTemp;
  6679. TraceCall("CIMAPSync::CmdCompletionNotification");
  6680. IxpAssert(NULL != lpszResponseText);
  6681. // Initialize variables
  6682. *szFmt = NULL;
  6683. // If we got any new unread messages, play sound and update tray icon
  6684. // Do it here instead of case tidFETCH_NEW_HDRS because if we were IDLE when we got
  6685. // new mail, m_sotCurrent is SOT_INVALID and we exit on the next if-statement
  6686. if (tidFETCH_NEW_HDRS == tid && m_fNewMail && m_fInbox &&
  6687. (NULL != m_pDefCallback || NULL != m_pCurrentCB))
  6688. {
  6689. IStoreCallback *pCB;
  6690. pCB = m_pDefCallback;
  6691. if (NULL == pCB)
  6692. pCB = m_pCurrentCB;
  6693. hrTemp = pCB->OnProgress(SOT_NEW_MAIL_NOTIFICATION, m_dwNumUnreadDLed, 0, NULL);
  6694. TraceError(hrTemp);
  6695. m_fNewMail = FALSE; // We've notified user
  6696. }
  6697. // We want to do the following even if there is no current operation
  6698. switch (tid)
  6699. {
  6700. case tidFETCH_NEW_HDRS:
  6701. case tidFETCH_CACHED_FLAGS:
  6702. case tidEXPUNGE:
  6703. case tidNOOP:
  6704. m_lSyncFolderRefCount -= 1;
  6705. break;
  6706. }
  6707. // We don't do a thing if there is no current operation (typ. means OnComplete
  6708. // already sent)
  6709. if (SOT_INVALID == m_sotCurrent)
  6710. return S_OK;
  6711. // Find out if this is a significant command which just completed
  6712. switch (tid)
  6713. {
  6714. case tidSELECTION:
  6715. // Select failure results in failure of current operation (eg, SOT_GET_MESSAGE
  6716. // or SOT_SYNC_FOLDER) but does not otherwise invoke a call to OnComplete
  6717. // This transaction ID identifies our mailbox SELECT attempt
  6718. if (SUCCEEDED(hrCompletionResult))
  6719. {
  6720. FOLDERINFO fiFolderInfo;
  6721. m_issCurrent = issSelected;
  6722. m_idSelectedFolder = m_idFolder;
  6723. // Set m_fInbox for new mail notification purposes
  6724. Assert(FALSE == m_fInbox); // Should only get set to TRUE in one place
  6725. hrTemp = m_pStore->GetFolderInfo(m_idSelectedFolder, &fiFolderInfo);
  6726. if (SUCCEEDED(hrTemp))
  6727. {
  6728. if (FOLDER_INBOX == fiFolderInfo.tySpecial)
  6729. m_fInbox = TRUE;
  6730. m_pStore->FreeRecord(&fiFolderInfo);
  6731. }
  6732. // Check if cached messages are still applicable to this folder
  6733. hrCompletionResult = CheckUIDValidity();
  6734. if (FAILED(hrCompletionResult))
  6735. {
  6736. fCompletion = TRUE;
  6737. LoadString(g_hLocRes, idsIMAPUIDValidityError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6738. break;
  6739. }
  6740. // Restore to-do flags
  6741. m_dwSyncToDo = m_dwSyncFolderFlags;
  6742. Assert(0 == (SYNC_FOLDER_NOOP & m_dwSyncFolderFlags)); // Should not be a standing order
  6743. if (ISFLAGSET(m_dwSyncFolderFlags, (SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS)))
  6744. m_dwSyncToDo |= SYNC_FOLDER_NOOP; // Subsequent FULL sync's can be replaced with NOOP
  6745. // Inform the Connection FSM of this event
  6746. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_SELECTCOMPLETE);
  6747. TraceError(hrTemp);
  6748. }
  6749. else
  6750. {
  6751. // Report error to user
  6752. fCompletion = TRUE;
  6753. LoadString(g_hLocRes, idsIMAPSelectFailureTextFmt, szFmt, ARRAYSIZE(szFmt));
  6754. wnsprintf(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), szFmt, (m_pszFldrLeafName ? m_pszFldrLeafName : ""));
  6755. }
  6756. break; // case tidSELECTION
  6757. case tidFETCH_NEW_HDRS:
  6758. fCompletion = TRUE; // We'll assume this unless we find otherwise
  6759. // This transaction ID identifies our attempt to fetch new msg headers
  6760. if (FAILED(hrCompletionResult))
  6761. {
  6762. LoadString(g_hLocRes, idsIMAPNewMsgDLErrText, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6763. }
  6764. else
  6765. {
  6766. if (FALSE == m_fMsgCountValid)
  6767. {
  6768. DWORD dwCachedCount;
  6769. // Svr didn't give us EXISTS (typ. NSCP v2.0). Assume m_cFilled == EXISTS
  6770. hrTemp = m_pFolder->GetRecordCount(IINDEX_PRIMARY, &dwCachedCount);
  6771. TraceError(hrTemp); // Record error but otherwise ignore
  6772. if (SUCCEEDED(hrTemp))
  6773. {
  6774. m_dwMsgCount = dwCachedCount; // I sure hope this is correct!
  6775. m_fMsgCountValid = TRUE;
  6776. }
  6777. }
  6778. // Launch next synchronization operation
  6779. hrCompletionResult = _SyncHeader();
  6780. if (FAILED(hrCompletionResult))
  6781. {
  6782. TraceResult(hrCompletionResult);
  6783. LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6784. }
  6785. else if (STORE_S_NOOP != hrCompletionResult || m_lSyncFolderRefCount > 0 ||
  6786. CFSM_STATE_WAITFORHDRSYNC != m_cfsState)
  6787. {
  6788. // Successfully launched next sync operation, so we're not done yet
  6789. fCompletion = FALSE;
  6790. }
  6791. }
  6792. if (SUCCEEDED(hrCompletionResult) && fCompletion)
  6793. {
  6794. // We're done with header sync (but not with the operation)
  6795. fCompletion = FALSE;
  6796. // Inform the Connection FSM of this event
  6797. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_HDRSYNCCOMPLETE);
  6798. TraceError(hrTemp);
  6799. }
  6800. break; // case tidFETCH_NEW_HDRS
  6801. case tidFETCH_CACHED_FLAGS:
  6802. fCompletion = TRUE; // We'll assume this unless we find otherwise
  6803. // If any errors occurred, bail out
  6804. if (FAILED(hrCompletionResult))
  6805. {
  6806. LoadString(g_hLocRes, idsIMAPOldMsgUpdateFailure, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6807. }
  6808. else
  6809. {
  6810. // Delete all msgs deleted from server since last sync-up
  6811. hrCompletionResult = SyncDeletedMessages();
  6812. if (FAILED(hrCompletionResult))
  6813. {
  6814. LoadString(g_hLocRes, idsIMAPMsgDeleteSyncErrText, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6815. }
  6816. // Check we if we did a full sync
  6817. if (ISFLAGSET(m_dwSyncFolderFlags, (SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS)))
  6818. m_fDidFullSync = TRUE;
  6819. // Launch next synchronization operation
  6820. hrCompletionResult = _SyncHeader();
  6821. if (FAILED(hrCompletionResult))
  6822. {
  6823. TraceResult(hrCompletionResult);
  6824. LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6825. break;
  6826. }
  6827. else if (STORE_S_NOOP != hrCompletionResult || m_lSyncFolderRefCount > 0 ||
  6828. CFSM_STATE_WAITFORHDRSYNC != m_cfsState)
  6829. {
  6830. // Successfully launched next sync operation, so we're not done yet
  6831. fCompletion = FALSE;
  6832. }
  6833. }
  6834. if (SUCCEEDED(hrCompletionResult) && fCompletion)
  6835. {
  6836. // We're done with header sync (but not with the operation)
  6837. fCompletion = FALSE;
  6838. // Inform the Connection FSM of this event
  6839. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_HDRSYNCCOMPLETE);
  6840. TraceError(hrTemp);
  6841. }
  6842. break; // case tidFETCH_CACHED_FLAGS
  6843. case tidEXPUNGE:
  6844. fCompletion = TRUE; // We'll assume this unless we find otherwise
  6845. // Launch next synchronization operation
  6846. if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  6847. {
  6848. hrCompletionResult = _SyncHeader();
  6849. if (FAILED(hrCompletionResult))
  6850. {
  6851. TraceResult(hrCompletionResult);
  6852. LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6853. break;
  6854. }
  6855. else if (STORE_S_NOOP != hrCompletionResult || m_lSyncFolderRefCount > 0 ||
  6856. CFSM_STATE_WAITFORHDRSYNC != m_cfsState)
  6857. {
  6858. // Successfully launched next sync operation, so we're not done yet
  6859. fCompletion = FALSE;
  6860. }
  6861. }
  6862. if (SUCCEEDED(hrCompletionResult) && fCompletion)
  6863. {
  6864. // We're done with header sync (but not with the operation)
  6865. fCompletion = FALSE;
  6866. // Inform the Connection FSM of this event
  6867. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_HDRSYNCCOMPLETE);
  6868. TraceError(hrTemp);
  6869. }
  6870. break; // case tidEXPUNGE
  6871. case tidBODYMSN:
  6872. if (SUCCEEDED(hrCompletionResult))
  6873. {
  6874. // We now have the MsgSeqNumToUID translation. Get the body
  6875. hrCompletionResult = _EnqueueOperation(tidBODY, lParam, icFETCH_COMMAND,
  6876. NULL, uiNORMAL_PRIORITY);
  6877. if (FAILED(hrCompletionResult))
  6878. TraceResult(hrCompletionResult);
  6879. else
  6880. break;
  6881. }
  6882. // *** If tidBODYMSN failed, FALL THROUGH and code below will handle failure
  6883. case tidBODY:
  6884. // As commented in CIMAPSync::UpdateMsgBody, FETCH has many failure modes
  6885. if (SUCCEEDED(hrCompletionResult))
  6886. {
  6887. if (OLE_E_BLANK != m_hrOperationResult && FAILED(m_hrOperationResult))
  6888. hrCompletionResult = m_hrOperationResult;
  6889. else if (FALSE == m_fGotBody)
  6890. hrCompletionResult = STORE_E_EXPIRED;
  6891. }
  6892. // Load error info if this failed
  6893. if (FAILED(hrCompletionResult))
  6894. LoadString(g_hLocRes, idsIMAPBodyFetchFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6895. // Commit message body to stream (or not, depending on success/failure)
  6896. NotifyMsgRecipients(lParam, fCOMPLETED, NULL, hrCompletionResult, m_szOperationProblem);
  6897. m_fGotBody = FALSE;
  6898. fCompletion = TRUE;
  6899. break;
  6900. case tidNOOP:
  6901. fCompletion = TRUE; // We'll assume this unless we find otherwise
  6902. // Launch next synchronization operation
  6903. if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  6904. {
  6905. hrCompletionResult = _SyncHeader();
  6906. if (FAILED(hrCompletionResult))
  6907. {
  6908. TraceResult(hrCompletionResult);
  6909. LoadString(g_hLocRes, idsGenericError, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6910. break;
  6911. }
  6912. else if (STORE_S_NOOP != hrCompletionResult || m_lSyncFolderRefCount > 0 ||
  6913. CFSM_STATE_WAITFORHDRSYNC != m_cfsState)
  6914. {
  6915. // Successfully launched next sync operation, so we're not done yet
  6916. fCompletion = FALSE;
  6917. }
  6918. }
  6919. if (SUCCEEDED(hrCompletionResult) && fCompletion)
  6920. {
  6921. // We're done with header sync (but not with the operation)
  6922. fCompletion = FALSE;
  6923. // Inform the Connection FSM of this event
  6924. hrTemp = _ConnFSM_QueueEvent(CFSM_EVENT_HDRSYNCCOMPLETE);
  6925. TraceError(hrTemp);
  6926. }
  6927. break; // case tidNOOP
  6928. case tidMARKMSGS:
  6929. {
  6930. MARK_MSGS_INFO *pMarkMsgInfo = (MARK_MSGS_INFO *) lParam;
  6931. // We're done now whether or not we succeeded/failed
  6932. sotOpType = pMarkMsgInfo->sotOpType;
  6933. pCallback = m_pCurrentCB;
  6934. SafeRelease(pMarkMsgInfo->pMsgRange);
  6935. // Defer freeing MessageIDList until we have time to use it
  6936. fCompletion = TRUE;
  6937. IxpAssert(NULL != pMarkMsgInfo);
  6938. TraceError(hrCompletionResult);
  6939. if (SUCCEEDED(hrCompletionResult))
  6940. {
  6941. // Update the IMessageFolder with the new server state
  6942. hrCompletionResult = m_pFolder->SetMessageFlags(pMarkMsgInfo->pList,
  6943. &pMarkMsgInfo->afFlags, NULL, NULL);
  6944. TraceError(hrCompletionResult);
  6945. }
  6946. SafeMemFree(pMarkMsgInfo->pList);
  6947. delete pMarkMsgInfo;
  6948. }
  6949. break; // case tidMARKMSGS
  6950. case tidCOPYMSGS:
  6951. {
  6952. IMAP_COPYMOVE_INFO *pCopyMoveInfo = (IMAP_COPYMOVE_INFO *) lParam;
  6953. BOOL fCopyDone;
  6954. // Check if this is the last SELECT command we sent out
  6955. IxpAssert(NULL != lParam);
  6956. fCopyDone = FALSE;
  6957. TraceError(hrCompletionResult);
  6958. if (FALSE == fCopyDone && SUCCEEDED(hrCompletionResult) &&
  6959. (COPY_MESSAGE_MOVE & pCopyMoveInfo->dwOptions))
  6960. {
  6961. ADJUSTFLAGS afFlags;
  6962. // Delete source messages as part of the move
  6963. afFlags.dwAdd = ARF_ENDANGERED;
  6964. afFlags.dwRemove = 0;
  6965. hrCompletionResult = _SetMessageFlags(SOT_COPYMOVE_MESSAGE, pCopyMoveInfo->pList,
  6966. &afFlags, m_pCurrentCB);
  6967. if (E_PENDING == hrCompletionResult)
  6968. {
  6969. hrCompletionResult = S_OK; // Suppress error
  6970. }
  6971. if (FAILED(hrCompletionResult))
  6972. {
  6973. TraceResult(hrCompletionResult);
  6974. fCopyDone = TRUE;
  6975. }
  6976. }
  6977. else
  6978. fCopyDone = TRUE;
  6979. if (FAILED(hrCompletionResult))
  6980. {
  6981. // Inform user of the error
  6982. IxpAssert(fCopyDone);
  6983. LoadString(g_hLocRes, idsIMAPCopyMsgsFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  6984. }
  6985. // Whether or not copy is done, we have to free the data
  6986. SafeMemFree(pCopyMoveInfo->pList);
  6987. SafeRelease(pCopyMoveInfo->pCopyRange);
  6988. if (fCopyDone)
  6989. {
  6990. // Set up callback information for OnComplete
  6991. sotOpType = SOT_COPYMOVE_MESSAGE;
  6992. pCallback = m_pCurrentCB;
  6993. fCompletion = TRUE;
  6994. }
  6995. delete pCopyMoveInfo;
  6996. }
  6997. break; // case tidCOPYMSGS
  6998. case tidUPLOADMSG:
  6999. {
  7000. APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) lParam;
  7001. // We're done the upload, whether the APPEND succeeded or failed
  7002. SafeMemFree(pAppendInfo->pszMsgFlags);
  7003. SafeRelease(pAppendInfo->lpstmMsg);
  7004. sotOpType = SOT_PUT_MESSAGE;
  7005. pCallback = m_pCurrentCB;
  7006. fCompletion = TRUE;
  7007. delete pAppendInfo;
  7008. // Inform the user of any errors
  7009. if (FAILED(hrCompletionResult))
  7010. LoadString(g_hLocRes, idsIMAPAppendFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7011. }
  7012. break; // case tidUPLOADMSG
  7013. case tidPREFIXLIST:
  7014. case tidPREFIX_HC:
  7015. case tidPREFIX_CREATE:
  7016. case tidFOLDERLIST:
  7017. case tidFOLDERLSUB:
  7018. case tidHIERARCHYCHAR_LIST_B:
  7019. case tidHIERARCHYCHAR_CREATE:
  7020. case tidHIERARCHYCHAR_LIST_C:
  7021. case tidHIERARCHYCHAR_DELETE:
  7022. case tidSPECIALFLDRLIST:
  7023. case tidSPECIALFLDRLSUB:
  7024. case tidSPECIALFLDRSUBSCRIBE:
  7025. hrCompletionResult = DownloadFoldersSequencer(tid, lParam,
  7026. hrCompletionResult, lpszResponseText, &fCompletion);
  7027. break; // DownloadFoldersSequencer transactions
  7028. case tidCREATE:
  7029. if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7030. {
  7031. CREATE_FOLDER_INFO *pcfi = (CREATE_FOLDER_INFO *) lParam;
  7032. // If CREATE returns tagged NO, folder may already exist. Issue LIST and find out!
  7033. if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7034. {
  7035. pcfi->dwFlags |= CFI_CREATEFAILURE;
  7036. StrCpyN(m_szOperationDetails, lpszResponseText, ARRAYSIZE(m_szOperationDetails));
  7037. }
  7038. // Add the folder to our foldercache, by listing it
  7039. hrCompletionResult = _EnqueueOperation(tidCREATELIST, lParam, icLIST_COMMAND,
  7040. pcfi->pszFullFolderPath, uiNORMAL_PRIORITY);
  7041. if (FAILED(hrCompletionResult))
  7042. {
  7043. TraceResult(hrCompletionResult);
  7044. fCreateDone = TRUE;
  7045. LoadString(g_hLocRes, idsIMAPCreateListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7046. }
  7047. }
  7048. else
  7049. {
  7050. // If we failed, notify user and free the folder pathname string
  7051. TraceResult(hrCompletionResult);
  7052. if (NULL != lParam)
  7053. fCreateDone = TRUE;
  7054. // Inform the user of the error.
  7055. LoadString(g_hLocRes, idsIMAPCreateFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7056. }
  7057. break; // case tidCREATE
  7058. case tidCREATELIST:
  7059. if (SUCCEEDED(hrCompletionResult) &&
  7060. (CFI_RECEIVEDLISTING & ((CREATE_FOLDER_INFO *)lParam)->dwFlags))
  7061. {
  7062. // We received a listing of this mailbox, so it's now cached. Subscribe it!
  7063. hrCompletionResult = _EnqueueOperation(tidCREATESUBSCRIBE, lParam,
  7064. icSUBSCRIBE_COMMAND, ((CREATE_FOLDER_INFO *)lParam)->pszFullFolderPath,
  7065. uiNORMAL_PRIORITY);
  7066. if (FAILED(hrCompletionResult))
  7067. {
  7068. TraceResult(hrCompletionResult);
  7069. fCreateDone = TRUE;
  7070. LoadString(g_hLocRes, idsIMAPCreateSubscribeFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7071. }
  7072. }
  7073. else
  7074. {
  7075. CREATE_FOLDER_INFO *pcfi = (CREATE_FOLDER_INFO *) lParam;
  7076. // Check if we were issuing a LIST in response to a failed CREATE command
  7077. if (CFI_CREATEFAILURE & pcfi->dwFlags)
  7078. {
  7079. LoadString(g_hLocRes, idsIMAPCreateFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7080. fSuppressDetails = TRUE; // Use response line from previous CREATE failure
  7081. hrCompletionResult = IXP_E_IMAP_TAGGED_NO_RESPONSE;
  7082. fCreateDone = TRUE;
  7083. break;
  7084. }
  7085. TraceError(hrCompletionResult);
  7086. if (SUCCEEDED(hrCompletionResult))
  7087. {
  7088. // The LIST was OK, but no folder name returned. This may mean
  7089. // we've assumed an incorrect hierarchy character
  7090. AssertSz(FALSE, "You might have an incorrect hierarchy char, here.");
  7091. hrCompletionResult = TraceResult(E_FAIL);
  7092. }
  7093. // If we failed, notify user and free the folder pathname string
  7094. if (NULL != lParam)
  7095. fCreateDone = TRUE;
  7096. // Inform the user of the error.
  7097. LoadString(g_hLocRes, idsIMAPCreateListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7098. }
  7099. break; // case tidCREATELIST
  7100. case tidCREATESUBSCRIBE:
  7101. // Whether we succeeded or not, free the CREATE_FOLDER_INFO
  7102. // (We're at the end of the create folder sequence)
  7103. if (NULL != lParam)
  7104. fCreateDone = TRUE;
  7105. // Remove this folder from LISTed folder list, if we were listing folders (this
  7106. // way the special folder we created doesn't get marked as unsubscribed)
  7107. if (NULL != m_pListHash && NULL != lParam)
  7108. {
  7109. CREATE_FOLDER_INFO *pcfiCreateInfo = (CREATE_FOLDER_INFO *) lParam;
  7110. FOLDERID idTemp;
  7111. hrTemp = m_pListHash->Find(
  7112. ImapUtil_ExtractLeafName(pcfiCreateInfo->pszFullFolderPath, m_cRootHierarchyChar),
  7113. fREMOVE, (void **) &idTemp);
  7114. TraceError(hrTemp);
  7115. }
  7116. // Check for errors.
  7117. if (FAILED(hrCompletionResult))
  7118. {
  7119. TraceResult(hrCompletionResult);
  7120. LoadString(g_hLocRes, idsIMAPCreateSubscribeFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7121. }
  7122. else if (NULL != lParam)
  7123. {
  7124. // Update the subscription status on the folder
  7125. IxpAssert(FOLDERID_INVALID != ((CREATE_FOLDER_INFO *)lParam)->idFolder);
  7126. hrCompletionResult = m_pStore->SubscribeToFolder(
  7127. ((CREATE_FOLDER_INFO *)lParam)->idFolder, fSUBSCRIBE, NOSTORECALLBACK);
  7128. TraceError(hrCompletionResult);
  7129. }
  7130. break; // case tidCREATESUBSCRIBE
  7131. case tidDELETEFLDR:
  7132. DELETE_FOLDER_INFO *pdfi;
  7133. pdfi = (DELETE_FOLDER_INFO *)lParam;
  7134. if (SUCCEEDED(hrCompletionResult))
  7135. {
  7136. // Unsubscribe the folder to complete the delete process
  7137. _EnqueueOperation(tidDELETEFLDR_UNSUBSCRIBE, lParam, icUNSUBSCRIBE_COMMAND,
  7138. pdfi->pszFullFolderPath, uiNORMAL_PRIORITY);
  7139. }
  7140. else
  7141. {
  7142. // If I unsubscribe a failed delete, user might never realize he has this
  7143. // folder kicking around. Therefore, don't unsubscribe.
  7144. // We won't be needing this information, anymore
  7145. if (pdfi)
  7146. {
  7147. MemFree(pdfi->pszFullFolderPath);
  7148. MemFree(pdfi);
  7149. }
  7150. // Inform the user of the error
  7151. IxpAssert(SOT_DELETE_FOLDER == m_sotCurrent);
  7152. sotOpType = m_sotCurrent;
  7153. pCallback = m_pCurrentCB;
  7154. fCompletion = TRUE;
  7155. LoadString(g_hLocRes, idsIMAPDeleteFldrFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7156. }
  7157. break; // case tidDELETEFLDR
  7158. case tidDONT_CARE:
  7159. hrCompletionResult = S_OK; // Suppress all error
  7160. break;
  7161. case tidDELETEFLDR_UNSUBSCRIBE:
  7162. // This folder is already deleted, so even if unsubscribe fails, delete from cache
  7163. fDelFldrFromCache = TRUE;
  7164. // Inform the user of any errors
  7165. if (FAILED(hrCompletionResult))
  7166. {
  7167. LoadString(g_hLocRes, idsIMAPDeleteFldrUnsubFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7168. }
  7169. break; // case tidDELETEFLDR_UNSUBSCRIBE
  7170. case tidCLOSE:
  7171. fCompletion = TRUE;
  7172. OnFolderExit();
  7173. m_issCurrent = issAuthenticated;
  7174. hrCompletionResult = S_OK; // Suppress error
  7175. break;
  7176. case tidSUBSCRIBE:
  7177. {
  7178. UINT uiErrorFmtID; // In case of error
  7179. uiErrorFmtID = 0;
  7180. fCompletion = TRUE;
  7181. if (SUCCEEDED(hrCompletionResult))
  7182. {
  7183. IxpAssert(FOLDERID_INVALID != m_idCurrent);
  7184. // Update store subscription status
  7185. hrCompletionResult = m_pStore->SubscribeToFolder(m_idCurrent, m_fSubscribe, NOSTORECALLBACK);
  7186. if (FAILED(hrCompletionResult))
  7187. {
  7188. TraceResult(hrCompletionResult);
  7189. uiErrorFmtID = m_fSubscribe ? idsIMAPSubscrAddErrorFmt :
  7190. idsIMAPUnsubRemoveErrorFmt;
  7191. }
  7192. }
  7193. else
  7194. {
  7195. TraceResult(hrCompletionResult);
  7196. uiErrorFmtID = m_fSubscribe ? idsIMAPSubscribeFailedFmt : idsIMAPUnsubscribeFailedFmt;
  7197. }
  7198. // Load error message, if an error occurred
  7199. if (FAILED(hrCompletionResult))
  7200. {
  7201. FOLDERINFO fiFolderInfo;
  7202. LoadString(g_hLocRes, uiErrorFmtID, szFmt, ARRAYSIZE(szFmt));
  7203. hrTemp = m_pStore->GetFolderInfo(m_idCurrent, &fiFolderInfo);
  7204. if (FAILED(hrTemp))
  7205. {
  7206. // Time to lie, cheat and STEAL!!!
  7207. TraceResult(hrTemp);
  7208. ZeroMemory(&fiFolderInfo, sizeof(fiFolderInfo));
  7209. fiFolderInfo.pszName = PszDupA(c_szFolderV1);
  7210. }
  7211. wnsprintf(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), szFmt, fiFolderInfo.pszName);
  7212. m_pStore->FreeRecord(&fiFolderInfo);
  7213. } // if (FAILED(hrCompletionResult))
  7214. } // case tidSUBSCRIBE
  7215. break; // case tidSUBSCRIBE
  7216. case tidRENAME:
  7217. case tidRENAMESUBSCRIBE:
  7218. case tidRENAMELIST:
  7219. case tidRENAMERENAME:
  7220. case tidRENAMESUBSCRIBE_AGAIN:
  7221. case tidRENAMEUNSUBSCRIBE:
  7222. hrCompletionResult = RenameSequencer(tid, lParam, hrCompletionResult,
  7223. lpszResponseText, &fCompletion);
  7224. break; // Rename operations
  7225. case tidSTATUS:
  7226. IxpAssert(FOLDERID_INVALID != (FOLDERID)lParam);
  7227. fCompletion = TRUE;
  7228. if (FAILED(hrCompletionResult))
  7229. {
  7230. FOLDERINFO fiFolderInfo;
  7231. // Construct descriptive error message
  7232. LoadString(g_hLocRes, idsGetUnreadCountFailureFmt, szFmt, ARRAYSIZE(szFmt));
  7233. if (SUCCEEDED(m_pStore->GetFolderInfo((FOLDERID) lParam, &fiFolderInfo)))
  7234. {
  7235. wnsprintf(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), szFmt, fiFolderInfo.pszName, m_szAccountName);
  7236. m_pStore->FreeRecord(&fiFolderInfo);
  7237. }
  7238. }
  7239. break;
  7240. default:
  7241. AssertSz(FALSE, "Unhandled transaction ID!");
  7242. break; // default case
  7243. }
  7244. // If we've finished a create folder (success/failure), tell them he can nuke us, now
  7245. if (fCreateDone)
  7246. {
  7247. CREATE_FOLDER_INFO *pcfiCreateInfo = (CREATE_FOLDER_INFO *)lParam;
  7248. if (FOLDER_NOTSPECIAL == pcfiCreateInfo->dwFinalSfType)
  7249. {
  7250. IxpAssert(SOT_INVALID != m_sotCurrent);
  7251. IxpAssert(PCO_NONE == pcfiCreateInfo->pcoNextOp); // Regular fldr creation no longer has any post-ops
  7252. fCompletion = TRUE;
  7253. pCallback = m_pCurrentCB;
  7254. sotOpType = m_sotCurrent;
  7255. MemFree(pcfiCreateInfo->pszFullFolderPath);
  7256. delete pcfiCreateInfo;
  7257. }
  7258. else
  7259. {
  7260. // We trying to create all the special folders: move on to the next one
  7261. if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7262. {
  7263. hrCompletionResult = CreateNextSpecialFolder(pcfiCreateInfo, &fCompletion);
  7264. TraceError(hrCompletionResult);
  7265. }
  7266. }
  7267. }
  7268. // If we've successfully deleted a folder, remove it from the foldercache
  7269. if (fDelFldrFromCache)
  7270. {
  7271. DELETE_FOLDER_INFO *pdfi = (DELETE_FOLDER_INFO *)lParam;
  7272. hrCompletionResult = DeleteFolderFromCache(pdfi->idFolder, fRECURSIVE);
  7273. if (FAILED(hrCompletionResult))
  7274. LoadString(g_hLocRes, idsErrDeleteCachedFolderFail, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7275. MemFree(pdfi->pszFullFolderPath);
  7276. MemFree(pdfi);
  7277. IxpAssert(SOT_DELETE_FOLDER == m_sotCurrent);
  7278. sotOpType = m_sotCurrent;
  7279. pCallback = m_pCurrentCB;
  7280. fCompletion = TRUE;
  7281. }
  7282. // Report command completion
  7283. if (fCompletion)
  7284. {
  7285. CONN_FSM_EVENT cfeEvent;
  7286. // Report command completion
  7287. if (FAILED(hrCompletionResult))
  7288. {
  7289. if (FALSE == fSuppressDetails)
  7290. StrCpyN(m_szOperationDetails, lpszResponseText, ARRAYSIZE(m_szOperationDetails));
  7291. cfeEvent = CFSM_EVENT_ERROR;
  7292. }
  7293. else
  7294. cfeEvent = CFSM_EVENT_OPERATIONCOMPLETE;
  7295. m_fTerminating = TRUE; // Either event should make us go to CFSM_STATE_OPERATIONCOMPLETE
  7296. m_hrOperationResult = hrCompletionResult;
  7297. // Check for user-induced connection drop, replace with non-UI error code
  7298. if (IXP_E_CONNECTION_DROPPED == hrCompletionResult && m_fDisconnecting)
  7299. m_hrOperationResult = STORE_E_OPERATION_CANCELED;
  7300. hrTemp = _ConnFSM_QueueEvent(cfeEvent);
  7301. TraceError(hrTemp);
  7302. // Might not want to do anything past this point, we might be all released
  7303. }
  7304. else if (CFSM_STATE_WAITFOROPERATIONDONE == m_cfsState)
  7305. {
  7306. // *** TEMPORARY until we remove CIMAPSync queueing code
  7307. do {
  7308. hrTemp = _SendNextOperation(NOFLAGS);
  7309. TraceError(hrTemp);
  7310. } while (S_OK == hrTemp);
  7311. }
  7312. return S_OK;
  7313. } // _OnCmdComplete
  7314. //***************************************************************************
  7315. // Function: DownloadFoldersSequencer
  7316. //
  7317. // Purpose:
  7318. // This function is a helper function for CmdCompletionNotification. I
  7319. // created it because the former function was getting big and unwieldy. I
  7320. // probably shouldn't have bothered, but now I'm too lazy to put it back.
  7321. // Besides, the comments for this function are going to be HUGE.
  7322. // This function contains all of the operations involved in a folder
  7323. // hierarchy download. In addition to the actual hierarchy download, this
  7324. // includes Root Folder Path (or Prefix) creation, and hierarchy character
  7325. // determination (often abbreviated HCF, where "F" is for Finding).
  7326. //
  7327. // Details: See the end of the module, where many details are provided.
  7328. //
  7329. // Arguments:
  7330. // WPARAM tid [in] - the wParam associated with this operation.
  7331. // LPARAM lParam [in] - the lParam associated with this operation, if any.
  7332. // HRESULT hrCompletionResult [in] - HRESULT indicating success or failure
  7333. // of the IMAP command.
  7334. // LPSTR lpszResponseText [in] - response text associated with the tagged
  7335. // response from the IMAP server.
  7336. // LPBOOL pfCompletion [out] - set to TRUE if current operation is finished.
  7337. //
  7338. // Returns:
  7339. // HRESULT indicating success or failure. This return value should be
  7340. // assigned to hrCompletionResult so that errors are displayed and the
  7341. // dialog taken down.
  7342. //***************************************************************************
  7343. HRESULT CIMAPSync::DownloadFoldersSequencer(const WPARAM wpTransactionID,
  7344. const LPARAM lParam,
  7345. HRESULT hrCompletionResult,
  7346. const LPCSTR lpszResponseText,
  7347. LPBOOL pfCompletion)
  7348. {
  7349. HRESULT hrTemp;
  7350. TraceCall("CIMAPSync::CIMAPFolderMgr::DownloadFoldersSequencer");
  7351. IxpAssert(m_cRef > 0);
  7352. IxpAssert(NULL != lpszResponseText);
  7353. IxpAssert(SOT_SYNCING_STORE == m_sotCurrent || SOT_PUT_MESSAGE == m_sotCurrent);
  7354. IxpAssert(NULL != pfCompletion);
  7355. IxpAssert(FALSE == *pfCompletion);
  7356. // Initialize variables
  7357. m_szOperationProblem[0] = '\0';
  7358. // Take action on the completion of certain commands
  7359. switch (wpTransactionID)
  7360. {
  7361. case tidPREFIXLIST:
  7362. AssertSz('\0' != m_szRootFolderPrefix[0], "You tried to list a blank folder. Brilliant.");
  7363. if (SUCCEEDED(hrCompletionResult))
  7364. {
  7365. // If we're looking for root-lvl hierarchy char, maybe this listing will help
  7366. if (NULL != m_phcfHierarchyCharInfo)
  7367. FindRootHierarchyChar(fHCF_PLAN_A_ONLY, lParam);
  7368. if (INVALID_HIERARCHY_CHAR == m_cRootHierarchyChar)
  7369. {
  7370. AssertSz(FALSE == m_fPrefixExists, "This doesn't make sense. Where's my HC?");
  7371. // List top level of hierarchy for hierarchy char determination
  7372. hrCompletionResult = _EnqueueOperation(tidPREFIX_HC, lParam,
  7373. icLIST_COMMAND, g_szPercent, uiNORMAL_PRIORITY);
  7374. TraceError(hrCompletionResult);
  7375. }
  7376. else
  7377. {
  7378. // We don't need to find HC - list the prefixed hierarchy or create prefix
  7379. if (m_fPrefixExists)
  7380. {
  7381. char szBuf[CCHMAX_IMAPFOLDERPATH+3];
  7382. // Prefix exists, so list it (only fixed buffers, so limited overflow risk)
  7383. wnsprintf(szBuf, ARRAYSIZE(szBuf), "%.512s%c*", m_szRootFolderPrefix, m_cRootHierarchyChar);
  7384. hrCompletionResult = _EnqueueOperation(tidFOLDERLIST, lParam,
  7385. icLIST_COMMAND, szBuf, uiNORMAL_PRIORITY);
  7386. TraceError(hrCompletionResult);
  7387. }
  7388. else
  7389. {
  7390. // Prefix doesn't exist, better create it
  7391. hrCompletionResult = CreatePrefix(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
  7392. TraceError(hrCompletionResult);
  7393. }
  7394. }
  7395. }
  7396. else
  7397. {
  7398. // Inform the user of the error.
  7399. TraceResult(hrCompletionResult);
  7400. LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7401. }
  7402. break; // case tidPREFIXLIST
  7403. case tidPREFIX_HC:
  7404. if (SUCCEEDED(hrCompletionResult))
  7405. {
  7406. // If we're looking for root-lvl hierarchy char, maybe this listing will help
  7407. AssertSz(NULL != m_phcfHierarchyCharInfo, "Why LIST % if you already KNOW HC?")
  7408. FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
  7409. // If Plan A for LIST % was sufficient to find HC, create prefix
  7410. if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
  7411. {
  7412. hrCompletionResult = CreatePrefix(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
  7413. TraceError(hrCompletionResult);
  7414. }
  7415. // else - Plan B has already been launched. Wait for its completion.
  7416. }
  7417. else
  7418. {
  7419. // Inform the user of the error.
  7420. TraceResult(hrCompletionResult);
  7421. LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7422. }
  7423. break; // case tidPREFIX_HC
  7424. case tidHIERARCHYCHAR_LIST_B:
  7425. // I don't care if this succeeded or failed. FindRootHierarchyChar will launch Plan C,
  7426. // if necessary. Suppress error-reporting due to failed tidHIERARCHYCHAR_LIST_B
  7427. IxpAssert(NULL != m_phcfHierarchyCharInfo);
  7428. IxpAssert(hcfPLAN_B == m_phcfHierarchyCharInfo->hcfStage);
  7429. if (FAILED(hrCompletionResult))
  7430. {
  7431. TraceResult(hrCompletionResult);
  7432. if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7433. hrCompletionResult = S_OK; // Suppress error-reporting - don't take down dialog
  7434. else
  7435. break;
  7436. }
  7437. FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
  7438. // If we found the hierarchy char, proceed with prefix OR special folder creation
  7439. if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
  7440. {
  7441. hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
  7442. TraceError(hrCompletionResult);
  7443. }
  7444. // else - Plan C has already been launched. Wait for its completion
  7445. break; // case tidHIERARCHYCHAR_LIST_B
  7446. case tidHIERARCHYCHAR_CREATE:
  7447. IxpAssert(NULL != m_phcfHierarchyCharInfo);
  7448. IxpAssert(hcfPLAN_C == m_phcfHierarchyCharInfo->hcfStage);
  7449. if (SUCCEEDED(hrCompletionResult))
  7450. {
  7451. // Try to list the folder for its juicy hierarchy char
  7452. hrCompletionResult = _EnqueueOperation(tidHIERARCHYCHAR_LIST_C, lParam,
  7453. icLIST_COMMAND, m_phcfHierarchyCharInfo->szTempFldrName,
  7454. uiNORMAL_PRIORITY);
  7455. TraceError(hrCompletionResult);
  7456. }
  7457. else if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7458. {
  7459. // Try the next plan in the list (which should succeed), and create prefix/special fldrs
  7460. TraceResult(hrCompletionResult);
  7461. FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
  7462. AssertSz(NULL == m_phcfHierarchyCharInfo,
  7463. "HEY, you added a new hierarchy char search plan and you didn't TELL ME!?");
  7464. hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
  7465. TraceError(hrCompletionResult);
  7466. }
  7467. break; // case tidHIERARCHYCHAR_CREATE
  7468. case tidHIERARCHYCHAR_LIST_C:
  7469. // I don't care if this succeeded or failed. Defer check for hierarchy
  7470. // char, we MUST delete the temp fldr, for now
  7471. IxpAssert(NULL != m_phcfHierarchyCharInfo);
  7472. IxpAssert(hcfPLAN_C == m_phcfHierarchyCharInfo->hcfStage);
  7473. if (FAILED(hrCompletionResult))
  7474. {
  7475. TraceResult(hrCompletionResult);
  7476. if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7477. hrCompletionResult = S_OK; // Suppress default error-handling - don't take down dialog
  7478. else
  7479. break;
  7480. }
  7481. hrCompletionResult = _EnqueueOperation(tidHIERARCHYCHAR_DELETE, lParam,
  7482. icDELETE_COMMAND, m_phcfHierarchyCharInfo->szTempFldrName, uiNORMAL_PRIORITY);
  7483. TraceError(hrCompletionResult);
  7484. break; // case tidHIERARCHYCHAR_LIST_C
  7485. case tidHIERARCHYCHAR_DELETE:
  7486. if (FAILED(hrCompletionResult))
  7487. {
  7488. TraceError(hrCompletionResult);
  7489. if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7490. hrCompletionResult = S_OK; // Suppress error
  7491. else
  7492. break;
  7493. }
  7494. // Look for hierarchy char - doesn't matter if delete failed, or not
  7495. FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
  7496. AssertSz(NULL == m_phcfHierarchyCharInfo,
  7497. "HEY, you added a new hierarchy char search plan and you didn't TELL ME!?");
  7498. // Proceed with prefix/special folder creation (I assume I've found the hierarchy char)
  7499. AssertSz(INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar,
  7500. "By this stage, I should have a HC - an assumed one, if necessary.");
  7501. hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem),
  7502. lParam, pfCompletion);
  7503. TraceError(hrCompletionResult);
  7504. break; // case tidHIERARCHYCHAR_DELETE
  7505. case tidFOLDERLIST:
  7506. if (SUCCEEDED(hrCompletionResult))
  7507. {
  7508. char szBuf[CCHMAX_IMAPFOLDERPATH+3];
  7509. // Launch LSUB * or LSUB <prefix>/* as appropriate
  7510. if ('\0' != m_szRootFolderPrefix[0])
  7511. // Construct prefix + * (only fixed buffers, so limited overflow risk)
  7512. wnsprintf(szBuf, ARRAYSIZE(szBuf), "%.512s%c*", m_szRootFolderPrefix, m_cRootHierarchyChar);
  7513. else
  7514. {
  7515. szBuf[0] = '*';
  7516. szBuf[1] = '\0';
  7517. }
  7518. hrCompletionResult = _EnqueueOperation(tidFOLDERLSUB, lParam,
  7519. icLSUB_COMMAND, szBuf, uiNORMAL_PRIORITY);
  7520. TraceError(hrCompletionResult);
  7521. }
  7522. else
  7523. {
  7524. // Inform the user of the error.
  7525. TraceResult(hrCompletionResult);
  7526. LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7527. }
  7528. break; // case tidFOLDERLIST
  7529. case tidPREFIX_CREATE:
  7530. if (FAILED(hrCompletionResult))
  7531. {
  7532. char szFmt[2*CCHMAX_STRINGRES];
  7533. // Inform the user of the error.
  7534. TraceResult(hrCompletionResult);
  7535. LoadString(g_hLocRes, idsIMAPPrefixCreateFailedFmt, szFmt, ARRAYSIZE(szFmt));
  7536. wnsprintf(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), szFmt, m_szRootFolderPrefix);
  7537. break;
  7538. }
  7539. // Check if we need to create special folders
  7540. m_fPrefixExists = TRUE; // Make sure PostHCD creates special fldrs instead of the prefix
  7541. hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
  7542. if (FAILED(hrCompletionResult) || FALSE == *pfCompletion)
  7543. {
  7544. // We're not ready to sync deleted folders just yet: special folders are being created
  7545. break;
  7546. }
  7547. // If we reached this point, tidPREFIX_CREATE was successful:
  7548. // Prefix was created. No need to list its hierarchy (it has none),
  7549. // and we'll assume it can take Inferiors. We're DONE!
  7550. // *** FALL THROUGH to tidFOLDERLSUB, to sync deleted folders ***
  7551. case tidFOLDERLSUB:
  7552. if (SUCCEEDED(hrCompletionResult))
  7553. {
  7554. if (NULL != m_phcfHierarchyCharInfo)
  7555. FindRootHierarchyChar(fHCF_ALL_PLANS, lParam);
  7556. if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
  7557. {
  7558. if (m_fCreateSpecial)
  7559. {
  7560. // We now have the hierarchy character (required to create special folders).
  7561. // Create special folders
  7562. hrCompletionResult = PostHCD(m_szOperationProblem, ARRAYSIZE(m_szOperationProblem), lParam, pfCompletion);
  7563. if (FAILED(hrCompletionResult))
  7564. {
  7565. TraceResult(hrCompletionResult);
  7566. break;
  7567. }
  7568. }
  7569. else
  7570. {
  7571. EndFolderList();
  7572. // Close the download folders dialog, IF we've found the hierarchy char
  7573. // If HC not found, Plan B has already been launched, so wait for its completion
  7574. if (FOLDERID_INVALID == (FOLDERID)lParam)
  7575. {
  7576. Assert(INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar);
  7577. *pfCompletion = TRUE;
  7578. }
  7579. }
  7580. } // if (INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar)
  7581. } // if (SUCCEEDED(hrCompletionResult))
  7582. else
  7583. {
  7584. // Inform the user of the error.
  7585. TraceResult(hrCompletionResult);
  7586. LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7587. }
  7588. break; // case tidFOLDERLSUB
  7589. case tidSPECIALFLDRSUBSCRIBE:
  7590. // Regardless of success/failure, subscribe this special folder locally!
  7591. hrTemp = m_pStore->SubscribeToFolder(((CREATE_FOLDER_INFO *)lParam)->idFolder,
  7592. fSUBSCRIBE, NOSTORECALLBACK);
  7593. TraceError(hrTemp);
  7594. hrCompletionResult = S_OK; // Suppress error
  7595. // *** FALL THROUGH ***
  7596. case tidSPECIALFLDRLIST:
  7597. case tidSPECIALFLDRLSUB:
  7598. if (SUCCEEDED(hrCompletionResult) || IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7599. hrCompletionResult = CreateNextSpecialFolder((CREATE_FOLDER_INFO *)lParam, pfCompletion);
  7600. if (FAILED(hrCompletionResult))
  7601. {
  7602. TraceResult(hrCompletionResult);
  7603. LoadString(g_hLocRes, idsCreateSpecialFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7604. }
  7605. break; // case tidSPECIALFLDRLIST, tidSPECIALFLDRLSUB, tidSPECIALFLDRSUBSCRIBE
  7606. default:
  7607. AssertSz(FALSE, "Hey, why is DownloadFoldersSequencer getting called?");
  7608. break; // default case
  7609. }; // switch (wpTransactionID)
  7610. if (FAILED(hrCompletionResult))
  7611. {
  7612. *pfCompletion = TRUE;
  7613. DisposeOfWParamLParam(wpTransactionID, lParam, hrCompletionResult);
  7614. }
  7615. return hrCompletionResult;
  7616. } // DownloadFoldersSequencer
  7617. // Details for DownloadFoldersSequencer (1/16/97, raych)
  7618. // ------------------------------------
  7619. // DownloadFoldersSequencer implements a somewhat complicated flow of execution.
  7620. // For a map of the execution flow, you can either create one from the function,
  7621. // or look on pp. 658-659 of my logbook. In any case, you can basically divide
  7622. // the execution flow into two categories, one for a prefixed account (ie, one
  7623. // with a Root Folder Path), and the flow for a non-prefixed account.
  7624. //
  7625. // (12/02/1998): This code is getting unmaintainable, but previous attempts to
  7626. // clean it up failed due to insufficient time. If you get the chance to re-write
  7627. // then please do. The process is greatly simplified if we assume IMAP4rev1 servers
  7628. // because then hierarchy character determination becomes a straightforward matter.
  7629. //
  7630. // For a non-prefixed account, the longest possible path is:
  7631. // 1) tidFOLDERLSUB (LIST *), syncs deleted msgs
  7632. // 2) tidHIERARCHYCHAR_LIST_B 3) tidHIERARCHYCHAR_CREATE
  7633. // 4) tidHIERARCHYCHAR_LIST_C 5) tidHIERARCHYCHAR_DELETE
  7634. // 6) Special Folder Creation (END).
  7635. //
  7636. // For a prefixed account, where the prefix already exists:
  7637. // 1) tidPREFIXLIST - this WILL discover HC
  7638. // 2) tidFOLDERLSUB (LIST <PREFIX><HC>*), syncs deleted msgs
  7639. // 3) Special Folder Creation (END).
  7640. //
  7641. // For a prefixed account, where the prefix does not exist:
  7642. // 1) tidPREFIXLIST
  7643. // 2) tidPREFIX_HC 3) tidHIERARCHYCHAR_LIST_B
  7644. // 4) tidHIERARCHYCHAR_CREATE 5) tidHIERARCHYCHAR_LIST_C
  7645. // 6) tidHIERARCHYCHAR_DELETE 7) tidPREFIX_CREATE, syncs deleted msgs
  7646. // 8) Special Folder Creation (END).
  7647. //***************************************************************************
  7648. //***************************************************************************
  7649. HRESULT CIMAPSync::PostHCD(LPSTR pszErrorDescription,
  7650. DWORD dwSizeOfErrorDescription,
  7651. LPARAM lParam, LPBOOL pfCompletion)
  7652. {
  7653. HRESULT hrResult;
  7654. CREATE_FOLDER_INFO *pcfiCreateInfo;
  7655. // First, we try to create the prefix
  7656. hrResult = CreatePrefix(pszErrorDescription, dwSizeOfErrorDescription,
  7657. lParam, pfCompletion);
  7658. if (FAILED(hrResult) || (SUCCEEDED(hrResult) && FALSE == *pfCompletion))
  7659. {
  7660. // Either we successfully launched tidPREFIX_CREATE or something failed.
  7661. // Return as if caller had called CreatePrefix directly.
  7662. goto exit;
  7663. }
  7664. // At this point, CreatePrefix has told us that we do not need to create a prefix
  7665. Assert(TRUE == *pfCompletion);
  7666. Assert(SUCCEEDED(hrResult));
  7667. // Start special folder creation
  7668. pcfiCreateInfo = new CREATE_FOLDER_INFO;
  7669. if (NULL == pcfiCreateInfo)
  7670. {
  7671. hrResult = TraceResult(E_OUTOFMEMORY);
  7672. goto exit;
  7673. }
  7674. pcfiCreateInfo->pszFullFolderPath = NULL;
  7675. pcfiCreateInfo->idFolder = FOLDERID_INVALID;
  7676. pcfiCreateInfo->dwFlags = 0;
  7677. pcfiCreateInfo->csfCurrentStage = CSF_INIT;
  7678. pcfiCreateInfo->dwCurrentSfType = FOLDER_INBOX;
  7679. pcfiCreateInfo->dwFinalSfType = FOLDER_MAX - 1;
  7680. pcfiCreateInfo->lParam = (LPARAM) FOLDERID_INVALID;
  7681. pcfiCreateInfo->pcoNextOp = PCO_NONE;
  7682. hrResult = CreateNextSpecialFolder(pcfiCreateInfo, pfCompletion);
  7683. if (FAILED(hrResult))
  7684. {
  7685. TraceResult(hrResult);
  7686. goto exit;
  7687. }
  7688. m_fCreateSpecial = FALSE;
  7689. exit:
  7690. return hrResult;
  7691. }
  7692. //***************************************************************************
  7693. // Function: CreatePrefix
  7694. //
  7695. // Purpose:
  7696. // This function is called after the hierarchy character is found. If the
  7697. // user specified a prefix, this function creates it. Otherwise, it takes
  7698. // down the dialog box (since HC discovery is the last step for non-prefixed
  7699. // accounts).
  7700. //
  7701. // Arguments:
  7702. // LPSTR pszErrorDescription [out] - if an error is encountered, this
  7703. // function deposits a description into this output buffer.
  7704. // DWORD dwSizeOfErrorDescription [in] - size of pszErrorDescription.
  7705. // LPARAM lParam [in] - lParam to issue with IMAP command.
  7706. //
  7707. // Returns:
  7708. // HRESULT indicating success or failure. This return value should be
  7709. // assigned to hrCompletionResult so that errors are displayed and the
  7710. // dialog taken down.
  7711. //***************************************************************************
  7712. HRESULT CIMAPSync::CreatePrefix(LPSTR pszErrorDescription,
  7713. DWORD dwSizeOfErrorDescription,
  7714. LPARAM lParam, LPBOOL pfCompletion)
  7715. {
  7716. char szBuf[CCHMAX_IMAPFOLDERPATH+2];
  7717. HRESULT hr = S_OK;
  7718. TraceCall("CIMAPSync::CreatePrefix");
  7719. IxpAssert(m_cRef > 0);
  7720. AssertSz(INVALID_HIERARCHY_CHAR != m_cRootHierarchyChar,
  7721. "How do you intend to create a prefix when you don't know HC?");
  7722. IxpAssert(NULL != pfCompletion);
  7723. IxpAssert(FALSE == *pfCompletion);
  7724. // Check if there IS a prefix to create
  7725. if ('\0' == m_szRootFolderPrefix[0] || m_fPrefixExists)
  7726. {
  7727. // No prefix to create. We are done: we've discovered the hierarchy character
  7728. *pfCompletion = TRUE;
  7729. goto exit;
  7730. }
  7731. // Create the prefix
  7732. if ('\0' != m_cRootHierarchyChar)
  7733. {
  7734. wnsprintf(szBuf, ARRAYSIZE(szBuf), "%.512s%c", m_szRootFolderPrefix, m_cRootHierarchyChar);
  7735. hr = _EnqueueOperation(tidPREFIX_CREATE, lParam, icCREATE_COMMAND,
  7736. szBuf, uiNORMAL_PRIORITY);
  7737. if (FAILED(hr))
  7738. {
  7739. TraceResult(hr);
  7740. goto exit;
  7741. }
  7742. }
  7743. else
  7744. {
  7745. // We have a prefix on a non-hierarchical IMAP server!
  7746. LoadString(g_hLocRes, idsIMAPNoHierarchyLosePrefix, pszErrorDescription, dwSizeOfErrorDescription);
  7747. hr = TraceResult(hrIMAP_E_NoHierarchy);
  7748. goto exit;
  7749. }
  7750. exit:
  7751. return hr;
  7752. }
  7753. void CIMAPSync::EndFolderList(void)
  7754. {
  7755. HRESULT hrTemp;
  7756. // Folder DL complete: Delete any folders in foldercache which weren't LISTed
  7757. // and unsubscribe any folders which weren't LSUBed
  7758. if (NULL != m_pCurrentHash)
  7759. {
  7760. hrTemp = DeleteHashedFolders(m_pCurrentHash);
  7761. TraceError(hrTemp);
  7762. }
  7763. if (NULL != m_pListHash)
  7764. {
  7765. hrTemp = UnsubscribeHashedFolders(m_pStore, m_pListHash);
  7766. TraceError(hrTemp);
  7767. }
  7768. }
  7769. //***************************************************************************
  7770. // Function: RenameSequencer
  7771. //
  7772. // Purpose:
  7773. // This function is a helper function for CmdCompletionNotification. It
  7774. // contains all of the sequencing operations required to perform a folder
  7775. // rename. For details, see the end of the function.
  7776. //
  7777. // Arguments:
  7778. // Same as for CmdCompletionNotification.
  7779. //
  7780. // Returns:
  7781. // HRESULT indicating success or failure.
  7782. //***************************************************************************
  7783. HRESULT CIMAPSync::RenameSequencer(const WPARAM wpTransactionID,
  7784. const LPARAM lParam,
  7785. HRESULT hrCompletionResult,
  7786. LPCSTR lpszResponseText,
  7787. LPBOOL pfDone)
  7788. {
  7789. CRenameFolderInfo *pRenameInfo;
  7790. BOOL fRenameDone;
  7791. TraceCall("CIMAPSync::RenameSequencer");
  7792. IxpAssert(m_cRef > 0);
  7793. IxpAssert(NULL != lpszResponseText);
  7794. // Initialize variables
  7795. pRenameInfo = (CRenameFolderInfo *) lParam;
  7796. fRenameDone = FALSE;
  7797. *pfDone = FALSE;
  7798. // Take action on the completion of certain commands
  7799. switch (wpTransactionID)
  7800. {
  7801. case tidRENAME:
  7802. if (SUCCEEDED(hrCompletionResult))
  7803. {
  7804. // Update the foldercache (ignore errors, it reports them itself)
  7805. // Besides, user can hopefully fix foldercache errors by refreshing folderlist
  7806. // Assume server did a hierarchical rename: if not, we fix
  7807. hrCompletionResult = m_pStore->RenameFolder(pRenameInfo->idRenameFolder,
  7808. ImapUtil_ExtractLeafName(pRenameInfo->pszNewFolderPath,
  7809. pRenameInfo->cHierarchyChar), NOFLAGS, NOSTORECALLBACK);
  7810. if (FAILED(hrCompletionResult))
  7811. {
  7812. TraceResult(hrCompletionResult);
  7813. lpszResponseText = c_szEmpty; // This isn't applicable anymore
  7814. LoadString(g_hLocRes, idsIMAPRenameFCUpdateFailure, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7815. fRenameDone = TRUE; // This puts a damper on things, yes?
  7816. break;
  7817. }
  7818. // Subscribe the renamed tree
  7819. hrCompletionResult = RenameTreeTraversal(tidRENAMESUBSCRIBE,
  7820. pRenameInfo, fINCLUDE_RENAME_FOLDER);
  7821. if (FAILED(hrCompletionResult))
  7822. {
  7823. TraceResult(hrCompletionResult);
  7824. lpszResponseText = c_szEmpty; // This isn't applicable anymore
  7825. LoadString(g_hLocRes, idsIMAPRenameSubscribeFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7826. fRenameDone = TRUE;
  7827. break;
  7828. }
  7829. // List the old tree to see if it still exists (exclude renamed fldr)
  7830. hrCompletionResult = RenameTreeTraversal(tidRENAMELIST,
  7831. pRenameInfo, fEXCLUDE_RENAME_FOLDER);
  7832. if (FAILED(hrCompletionResult))
  7833. {
  7834. TraceResult(hrCompletionResult);
  7835. lpszResponseText = c_szEmpty; // This isn't applicable anymore
  7836. // Let's reuse the string for folder list failure
  7837. LoadString(g_hLocRes, idsIMAPFolderListFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7838. }
  7839. // Arm the trigger for Phase Two launch
  7840. pRenameInfo->fPhaseOneSent = TRUE;
  7841. } // if (SUCCEEDED(hrCompletionResult))
  7842. else
  7843. {
  7844. // Inform the user of any errors
  7845. TraceResult(hrCompletionResult);
  7846. LoadString(g_hLocRes, idsIMAPRenameFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7847. fRenameDone = TRUE;
  7848. }
  7849. break; // case tidRENAME
  7850. case tidRENAMESUBSCRIBE:
  7851. // Count the number of failed subscriptions
  7852. if (FAILED(hrCompletionResult))
  7853. {
  7854. TraceResult(hrCompletionResult);
  7855. pRenameInfo->iNumFailedSubs += 1;
  7856. if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7857. hrCompletionResult = S_OK; // Suppress failure report
  7858. }
  7859. lpszResponseText = c_szEmpty; // This isn't applicable anymore
  7860. // Decrement subscribe response counter, Watch for phase 2 launch condition
  7861. pRenameInfo->iNumSubscribeRespExpected -= 1;
  7862. if (0 == pRenameInfo->iNumSubscribeRespExpected)
  7863. {
  7864. HRESULT hrTemp;
  7865. // Theoretically, all subfolders of renamed folder are now subscribed
  7866. hrTemp = SubscribeSubtree(pRenameInfo->idRenameFolder, fSUBSCRIBE);
  7867. TraceError(hrTemp);
  7868. }
  7869. if (EndOfRenameFolderPhaseOne(pRenameInfo) && SUCCEEDED(hrCompletionResult))
  7870. {
  7871. // It is time to start the next phase of the operation
  7872. hrCompletionResult = RenameFolderPhaseTwo(pRenameInfo, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7873. TraceError(hrCompletionResult);
  7874. }
  7875. break; // case tidRENAMESUBSCRIBE
  7876. case tidRENAMELIST:
  7877. if (FAILED(hrCompletionResult))
  7878. {
  7879. TraceResult(hrCompletionResult);
  7880. if (IXP_E_IMAP_TAGGED_NO_RESPONSE == hrCompletionResult)
  7881. hrCompletionResult = S_OK; // Suppress failure report
  7882. }
  7883. lpszResponseText = c_szEmpty; // This isn't applicable anymore
  7884. // Count the number of list responses returned. Watch for phase 2 launch condition
  7885. pRenameInfo->iNumListRespExpected -= 1;
  7886. if (EndOfRenameFolderPhaseOne(pRenameInfo) && SUCCEEDED(hrCompletionResult))
  7887. {
  7888. // It is time to start the next phase of the operation
  7889. hrCompletionResult = RenameFolderPhaseTwo(pRenameInfo, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7890. TraceError(hrCompletionResult);
  7891. }
  7892. break; // case tidRENAMELIST
  7893. case tidRENAMERENAME:
  7894. // Failure will not be tolerated
  7895. if (FAILED(hrCompletionResult))
  7896. {
  7897. TraceResult(hrCompletionResult);
  7898. LoadString(g_hLocRes, idsIMAPAtomicRenameFailed, m_szOperationProblem, ARRAYSIZE(m_szOperationProblem));
  7899. }
  7900. lpszResponseText = c_szEmpty; // This isn't applicable anymore
  7901. // Decrement the (second) rename counts, watch for phase 2 launch condition
  7902. pRenameInfo->iNumRenameRespExpected -= 1;
  7903. if (EndOfRenameFolderPhaseTwo(pRenameInfo))
  7904. fRenameDone = TRUE;
  7905. break; // tidRENAMERENAME
  7906. case tidRENAMESUBSCRIBE_AGAIN:
  7907. // Modify the number of failed subscriptions based on success
  7908. if (SUCCEEDED(hrCompletionResult))
  7909. pRenameInfo->iNumFailedSubs -= 1;
  7910. else
  7911. pRenameInfo->iNumFailedSubs += 1;
  7912. hrCompletionResult = S_OK; // Suppress failure report
  7913. lpszResponseText = c_szEmpty; // This isn't applicable anymore
  7914. // Count the number of subscribe responses returned, watch for end-of-operation
  7915. pRenameInfo->iNumSubscribeRespExpected -= 1;
  7916. if (0 == pRenameInfo->iNumSubscribeRespExpected)
  7917. {
  7918. // Theoretically, all subfolders of renamed folder are now subscribed
  7919. hrCompletionResult = SubscribeSubtree(pRenameInfo->idRenameFolder, fSUBSCRIBE);
  7920. TraceError(hrCompletionResult);
  7921. }
  7922. if (EndOfRenameFolderPhaseTwo(pRenameInfo))
  7923. fRenameDone = TRUE;
  7924. break; // case tidRENAMESUBSCRIBE_AGAIN
  7925. case tidRENAMEUNSUBSCRIBE:
  7926. // Count the number of failed unsubscribe's, to report to user at end-of-operation
  7927. if (FAILED(hrCompletionResult))
  7928. {
  7929. TraceResult(hrCompletionResult);
  7930. pRenameInfo->iNumFailedUnsubs += 1;
  7931. }
  7932. hrCompletionResult = S_OK; // Suppress failure report
  7933. lpszResponseText = c_szEmpty; // This isn't applicable anymore
  7934. // Count the number of unsubscribe responses returned, watch for end-of-operation
  7935. pRenameInfo->iNumUnsubscribeRespExpected -= 1;
  7936. if (EndOfRenameFolderPhaseTwo(pRenameInfo))
  7937. fRenameDone = TRUE;
  7938. break; // case tidRENAMEUNSUBSCRIBE
  7939. default:
  7940. AssertSz(FALSE, "This is not an understood rename operation.");
  7941. break; // default case
  7942. } // switch (wpTransactionID)
  7943. // That's one less rename command pending from the server
  7944. pRenameInfo->Release();
  7945. *pfDone = fRenameDone;
  7946. return hrCompletionResult;
  7947. } // RenameSequencer
  7948. // Details for RenameSequencer (2/4/97, raych)
  7949. // ---------------------------
  7950. // A rename operation includes the original rename, subscription tracking, and
  7951. // atomic rename simulation (for Cyrus servers). To perform this, the rename
  7952. // operation is divided into two phases:
  7953. //
  7954. // PHASE ONE:
  7955. // 1) Assume rename was atomic. Subscribe new (renamed) folder hierarchy.
  7956. // 2) List first child of old rename folder, to check if rename was in fact atomic.
  7957. //
  7958. // PHASE TWO:
  7959. // 1) If rename was not atomic, issue a RENAME for each child of rename folder
  7960. // in order to SIMULATE an atomic rename. This does not check for collisions
  7961. // in the renamed space.
  7962. // 2) If the rename was not atomic, try to subscribe the new (renamed) folder
  7963. // hierarchy, again.
  7964. // 3) Unsubscribe the old folder hierarchy.
  7965. //
  7966. // What a pain.
  7967. //***************************************************************************
  7968. // Function: EndOfRenameFolderPhaseOne
  7969. //
  7970. // Purpose:
  7971. // This function detects whether Phase One of the rename operation has
  7972. // completed.
  7973. //
  7974. // Arguments:
  7975. // CRenameFolderInfo *pRenameInfo [in] - the CRenameFolderInfo associated
  7976. // with the RENAME operation.
  7977. //
  7978. // Returns:
  7979. // TRUE if Phase One has ended, otherwise FALSE. Phase One cannot end
  7980. // if it has not been sent, yet.
  7981. //***************************************************************************
  7982. inline BOOL CIMAPSync::EndOfRenameFolderPhaseOne(CRenameFolderInfo *pRenameInfo)
  7983. {
  7984. if (pRenameInfo->fPhaseOneSent &&
  7985. pRenameInfo->iNumSubscribeRespExpected <= 0 &&
  7986. pRenameInfo->iNumListRespExpected <= 0)
  7987. {
  7988. IxpAssert(0 == pRenameInfo->iNumSubscribeRespExpected);
  7989. IxpAssert(0 == pRenameInfo->iNumListRespExpected);
  7990. return TRUE; // This marks the end of phase one
  7991. }
  7992. else
  7993. return FALSE;
  7994. } // EndOfRenameFolderPhaseOne
  7995. //***************************************************************************
  7996. // Function: EndOfRenameFolderPhaseTwo
  7997. //
  7998. // Purpose:
  7999. // This function detects whether Phase Two of the rename operation has
  8000. // completed.
  8001. //
  8002. // Arguments:
  8003. // CRenameFolderInfo *pRenameInfo [in] - the CRenameFolderInfo associated
  8004. // with the RENAME operation.
  8005. //
  8006. // Returns:
  8007. // TRUE if Phase Two has ended, otherwise FALSE. Phase Two cannot end
  8008. // if it has not been sent, yet.
  8009. //***************************************************************************
  8010. inline BOOL CIMAPSync::EndOfRenameFolderPhaseTwo(CRenameFolderInfo *pRenameInfo)
  8011. {
  8012. if (pRenameInfo->fPhaseTwoSent &&
  8013. pRenameInfo->iNumRenameRespExpected <= 0 &&
  8014. pRenameInfo->iNumSubscribeRespExpected <= 0 &&
  8015. pRenameInfo->iNumUnsubscribeRespExpected <= 0)
  8016. {
  8017. IxpAssert(0 == pRenameInfo->iNumRenameRespExpected);
  8018. IxpAssert(0 == pRenameInfo->iNumSubscribeRespExpected);
  8019. IxpAssert(0 == pRenameInfo->iNumUnsubscribeRespExpected);
  8020. return TRUE; // This marks the end of phase two
  8021. }
  8022. else
  8023. return FALSE;
  8024. } // EndOfRenameFolderPhaseTwo
  8025. //***************************************************************************
  8026. // Function: RenameFolderPhaseTwo
  8027. //
  8028. // Purpose:
  8029. // This function launches Phase Two of the RENAME operation.
  8030. //
  8031. // Arguments:
  8032. // CRenameFolderInfo *pRenameInfo [in] - the CRenameFolderInfo associated
  8033. // with the RENAME operation.
  8034. // LPSTR szErrorDescription [in] - if an error occurs, this function
  8035. // deposits a desription in this buffer.
  8036. // DWORD dwSizeOfErrorDescription [in] - size of szErrorDescription.
  8037. //
  8038. // Returns:
  8039. // HRESULT indicating success or failure.
  8040. //***************************************************************************
  8041. HRESULT CIMAPSync::RenameFolderPhaseTwo(CRenameFolderInfo *pRenameInfo,
  8042. LPSTR szErrorDescription,
  8043. DWORD dwSizeOfErrorDescription)
  8044. {
  8045. HRESULT hrCompletionResult;
  8046. // Rename subfolders, re-attempt subscription of renamed tree, exclude rename folder
  8047. if (pRenameInfo->fNonAtomicRename)
  8048. {
  8049. hrCompletionResult = RenameTreeTraversal(tidRENAMERENAME,
  8050. pRenameInfo, fEXCLUDE_RENAME_FOLDER);
  8051. if (FAILED(hrCompletionResult))
  8052. {
  8053. TraceResult(hrCompletionResult);
  8054. LoadString(g_hLocRes, idsIMAPAtomicRenameFailed, szErrorDescription,
  8055. dwSizeOfErrorDescription);
  8056. goto exit;
  8057. }
  8058. hrCompletionResult = RenameTreeTraversal(tidRENAMESUBSCRIBE_AGAIN,
  8059. pRenameInfo, fEXCLUDE_RENAME_FOLDER);
  8060. if (FAILED(hrCompletionResult))
  8061. {
  8062. TraceResult(hrCompletionResult);
  8063. LoadString(g_hLocRes, idsIMAPRenameSubscribeFailed, szErrorDescription,
  8064. dwSizeOfErrorDescription);
  8065. goto exit;
  8066. }
  8067. }
  8068. // Unsubscribe from the old tree, include rename folder
  8069. hrCompletionResult = RenameTreeTraversal(tidRENAMEUNSUBSCRIBE,
  8070. pRenameInfo, fINCLUDE_RENAME_FOLDER);
  8071. if (FAILED(hrCompletionResult))
  8072. {
  8073. TraceResult(hrCompletionResult);
  8074. LoadString(g_hLocRes, idsIMAPRenameUnsubscribeFailed, szErrorDescription,
  8075. dwSizeOfErrorDescription);
  8076. goto exit;
  8077. }
  8078. // Arm the trigger for end-of-operation launch
  8079. pRenameInfo->fPhaseTwoSent = TRUE;
  8080. exit:
  8081. return hrCompletionResult;
  8082. } // RenameFolderPhaseTwo
  8083. //***************************************************************************
  8084. // Function: _OnMailBoxList
  8085. // Description: Helper function for OnResponse.
  8086. //
  8087. // This function saves the information from the LIST/LSUB command to the
  8088. // folder cache. If the folder already exists in the folder cache, its
  8089. // mailbox flags are updated. If it does not exist, a handle (and message
  8090. // cache filename) are reserved for the folder, and it is entered into
  8091. // the folder cache. This function is also part of the hierarchy character
  8092. // determination code. If a hierarchy character is encountered during a
  8093. // folder hierarchy download, we assume this is the hierarchy character
  8094. // for root-level folders.
  8095. //
  8096. // Arguments:
  8097. // WPARAM wpTransactionID [in] - the wParam of this operation (eg,
  8098. // tidFOLDERLSUB or tidCREATELIST).
  8099. // LPARAM lParam [in] - the lParam of this operation.
  8100. // LPSTR pszMailboxName [in] - the mailbox name returned via the LIST
  8101. // response, eg "INBOX".
  8102. // IMAP_MBOXFLAGS imfMboxFlags [in] - the mailbox flags returned via the
  8103. // LIST response, eg "\NoSelect".
  8104. // char cHierarchyChar [in] - the hierarchy character returned via the
  8105. // LIST response, eg '/'.
  8106. //***************************************************************************
  8107. HRESULT CIMAPSync::_OnMailBoxList( WPARAM tid,
  8108. LPARAM lParam,
  8109. LPSTR pszMailboxName,
  8110. IMAP_MBOXFLAGS imfMboxFlags,
  8111. char cHierarchyChar,
  8112. BOOL fNoTranslation)
  8113. {
  8114. const DWORD dwProgressInterval = 1;
  8115. HRESULT hr = S_OK;
  8116. FOLDERID idNewFolder = FOLDERID_INVALID;
  8117. FOLDERID idTemp = FOLDERID_INVALID;
  8118. BOOL fHandledLPARAM = FALSE;
  8119. LPSTR pszLocalPath = NULL;
  8120. BOOL fValidPrefix;
  8121. SPECIALFOLDER sfType;
  8122. BOOL fFreeLocalPath = FALSE;
  8123. TraceCall("CIMAPSync::_OnMailBoxList");
  8124. IxpAssert(m_cRef > 0);
  8125. IxpAssert(NULL != pszMailboxName);
  8126. // Hierarchy-character determination code
  8127. if (NULL != m_phcfHierarchyCharInfo)
  8128. {
  8129. switch (cHierarchyChar)
  8130. {
  8131. case '\0':
  8132. // If our prefix is not INBOX, we MUST treat NIL as a valid hierarchy char
  8133. if (tidPREFIXLIST == tid ||
  8134. 0 != lstrcmpi(pszMailboxName, c_szInbox))
  8135. m_phcfHierarchyCharInfo->fNonInboxNIL_Seen = TRUE;
  8136. break;
  8137. case '.':
  8138. m_phcfHierarchyCharInfo->fDotHierarchyCharSeen = TRUE;
  8139. break;
  8140. default:
  8141. // Set the bit in the array which corresponds to this character
  8142. m_phcfHierarchyCharInfo->bHierarchyCharBitArray[cHierarchyChar/8] |=
  8143. (1 << cHierarchyChar%8);
  8144. break;
  8145. }
  8146. }
  8147. // Remove prefix from full folder path
  8148. pszLocalPath = RemovePrefixFromPath(m_szRootFolderPrefix, pszMailboxName,
  8149. cHierarchyChar, &fValidPrefix, &sfType);
  8150. // Replace leading INBOX with localized folder name
  8151. const int c_iLenOfINBOX = 5; // Let me know if this changes
  8152. Assert(lstrlen(c_szINBOX) == c_iLenOfINBOX);
  8153. if (0 == StrCmpNI(pszLocalPath, c_szINBOX, c_iLenOfINBOX))
  8154. {
  8155. char cNextChar;
  8156. cNextChar = pszLocalPath[c_iLenOfINBOX];
  8157. if ('\0' == cNextChar || cHierarchyChar == cNextChar)
  8158. {
  8159. BOOL fResult;
  8160. int iLocalizedINBOXLen;
  8161. int cchNewPathLen;
  8162. char szInbox[CCHMAX_STRINGRES];
  8163. LPSTR pszNew;
  8164. // We found INBOX or INBOX<HC>: replace INBOX with localized version
  8165. Assert(FOLDER_INBOX == sfType || '\0' != cNextChar);
  8166. iLocalizedINBOXLen = LoadString(g_hLocRes, idsInbox, szInbox, ARRAYSIZE(szInbox));
  8167. cchNewPathLen = iLocalizedINBOXLen + lstrlen(pszLocalPath + c_iLenOfINBOX) + 1;
  8168. fResult = MemAlloc((void **)&pszNew, cchNewPathLen * sizeof(pszNew[0]));
  8169. if (FALSE == fResult)
  8170. {
  8171. hr = TraceResult(E_OUTOFMEMORY);
  8172. goto exit;
  8173. }
  8174. StrCpyN(pszNew, szInbox, cchNewPathLen);
  8175. StrCatBuff(pszNew, (pszLocalPath + c_iLenOfINBOX), cchNewPathLen);
  8176. pszLocalPath = pszNew;
  8177. fFreeLocalPath = TRUE;
  8178. }
  8179. }
  8180. // Add folder to foldercache if current operation warrants it (LIST only, ignore LSUB)
  8181. switch (tid)
  8182. {
  8183. case tidSPECIALFLDRLIST:
  8184. case tidFOLDERLIST:
  8185. case tidCREATELIST:
  8186. if (fValidPrefix && pszLocalPath[0] != '\0')
  8187. {
  8188. DWORD dwAFTCFlags;
  8189. dwAFTCFlags = (fNoTranslation ? AFTC_NOTRANSLATION : 0);
  8190. hr = AddFolderToCache(pszLocalPath, imfMboxFlags, cHierarchyChar,
  8191. dwAFTCFlags, &idNewFolder, sfType);
  8192. if (FAILED(hr))
  8193. {
  8194. TraceResult(hr);
  8195. goto exit;
  8196. }
  8197. }
  8198. }
  8199. // Tie up loose ends and exit
  8200. switch (tid)
  8201. {
  8202. // Are we looking for a prefix listing?
  8203. case tidPREFIXLIST:
  8204. IxpAssert(0 == lstrcmpi(pszMailboxName, m_szRootFolderPrefix));
  8205. m_fPrefixExists = TRUE;
  8206. fHandledLPARAM = TRUE;
  8207. goto exit; // Skip addition to foldercache
  8208. case tidRENAMELIST:
  8209. if (NULL != lParam)
  8210. {
  8211. // Well, looks like we have some subfolders to rename
  8212. ((CRenameFolderInfo *)lParam)->fNonAtomicRename = TRUE;
  8213. fHandledLPARAM = TRUE;
  8214. }
  8215. break;
  8216. case tidSPECIALFLDRLIST:
  8217. case tidFOLDERLIST:
  8218. case tidCREATELIST:
  8219. fHandledLPARAM = TRUE;
  8220. // Only act on validly prefixed folders
  8221. if (fValidPrefix && NULL != m_pCurrentHash)
  8222. {
  8223. // Remove LISTed folder from m_pCurrentHash (list of cached folders)
  8224. hr = m_pCurrentHash->Find(pszLocalPath, fREMOVE, (void **)&idTemp);
  8225. if (FAILED(hr))
  8226. {
  8227. if (FOLDERID_INVALID != idNewFolder)
  8228. idTemp = idNewFolder;
  8229. else
  8230. idTemp = FOLDERID_INVALID;
  8231. }
  8232. // NOTE that it is possible for idTemp != idNewFolder. This occurs if
  8233. // I change RFP from "" to "aaa" and there exists two folders, "bbb" and
  8234. // "aaa/bbb". Believe it or not, this happened to me during rudimentary testing.
  8235. // The correct folder to use in this case is idTemp, which is determined using FULL path
  8236. // Record all LISTed folders in m_pListHash
  8237. if (NULL != m_pListHash)
  8238. {
  8239. hr = m_pListHash->Insert(pszLocalPath, idTemp, HF_NO_DUPLICATES);
  8240. TraceError(hr);
  8241. }
  8242. }
  8243. if (tidCREATELIST != tid && FALSE == (tidSPECIALFLDRLIST == tid && NULL != lParam &&
  8244. 0 == lstrcmpi(pszMailboxName, ((CREATE_FOLDER_INFO *)lParam)->pszFullFolderPath)))
  8245. break;
  8246. // *** FALL THROUGH if tidCREATELIST, or tidSPECIALFLDRLIST and exact path match ***
  8247. if (NULL != lParam)
  8248. {
  8249. CREATE_FOLDER_INFO *pcfi = (CREATE_FOLDER_INFO *) lParam;
  8250. // Inform cmd completion that it's OK to send the subscribe cmd
  8251. // Also record fldrID of new fldr so we can update store after successful subscribe
  8252. pcfi->dwFlags |= CFI_RECEIVEDLISTING;
  8253. pcfi->idFolder = idNewFolder;
  8254. fHandledLPARAM = TRUE;
  8255. }
  8256. break;
  8257. case tidSPECIALFLDRLSUB:
  8258. case tidFOLDERLSUB:
  8259. // Verify that we already received this folderpath via a LIST response
  8260. // If we DID receive this folder via LIST, remove from m_pListHash
  8261. fHandledLPARAM = TRUE;
  8262. // Only act on validly prefixed folders
  8263. if (fValidPrefix)
  8264. {
  8265. hr = m_pListHash->Find(pszLocalPath, fREMOVE, (void **)&idTemp);
  8266. if (SUCCEEDED(hr))
  8267. {
  8268. // This folder was received via LIST and thus it exists: subscribe it
  8269. if (FOLDERID_INVALID != idTemp)
  8270. {
  8271. hr = m_pStore->SubscribeToFolder(idTemp, fSUBSCRIBE, NOSTORECALLBACK);
  8272. TraceError(hr);
  8273. }
  8274. }
  8275. else
  8276. {
  8277. DWORD dwTranslateFlags;
  8278. HRESULT hrTemp;
  8279. // This folder was not returned via LIST. Destroy it
  8280. hrTemp = FindHierarchicalFolderName(pszLocalPath, cHierarchyChar,
  8281. &idTemp, pahfoDONT_CREATE_FOLDER);
  8282. if (SUCCEEDED(hrTemp))
  8283. {
  8284. // Do record result of this in hr because failure here is not cool
  8285. hr = DeleteFolderFromCache(idTemp, fNON_RECURSIVE);
  8286. TraceError(hr);
  8287. }
  8288. // if FAILED(hr), we probably never cached this folder, so ignore it
  8289. // Unsubscribe it regardless of whether it was in the foldercache
  8290. // If this folder is fNoTranslation, we have to disable translation for this
  8291. // call to UNSUBSCRIBE. Otherwise IIMAPTransport2 should already have translation enabled
  8292. hrTemp = S_OK;
  8293. if (fNoTranslation)
  8294. {
  8295. dwTranslateFlags = IMAP_MBOXXLATE_VERBATIMOK | IMAP_MBOXXLATE_RETAINCP |
  8296. IMAP_MBOXXLATE_DISABLE;
  8297. hrTemp = m_pTransport->SetDefaultCP(dwTranslateFlags, 0);
  8298. }
  8299. if (SUCCEEDED(hrTemp))
  8300. {
  8301. hrTemp = _EnqueueOperation(tidDONT_CARE, 0, icUNSUBSCRIBE_COMMAND,
  8302. pszMailboxName, uiNORMAL_PRIORITY);
  8303. TraceError(hrTemp);
  8304. }
  8305. // Restore translation mode to default (luckily we always know translation mode
  8306. // of a folder list)
  8307. if (fNoTranslation)
  8308. {
  8309. dwTranslateFlags &= ~(IMAP_MBOXXLATE_DISABLE);
  8310. dwTranslateFlags |= IMAP_MBOXXLATE_DEFAULT;
  8311. hrTemp = m_pTransport->SetDefaultCP(dwTranslateFlags, 0);
  8312. }
  8313. }
  8314. }
  8315. if (tidSPECIALFLDRLSUB == tid && NULL != lParam &&
  8316. 0 == (IMAP_MBOX_NOSELECT & imfMboxFlags))
  8317. {
  8318. // Inform cmd completion that there's no need to subscribe special folder
  8319. if (0 == lstrcmpi(pszMailboxName, ((CREATE_FOLDER_INFO *)lParam)->pszFullFolderPath))
  8320. ((CREATE_FOLDER_INFO *)lParam)->dwFlags |= CFI_RECEIVEDLISTING;
  8321. }
  8322. break;
  8323. case tidHIERARCHYCHAR_LIST_B:
  8324. case tidHIERARCHYCHAR_LIST_C:
  8325. case tidPREFIX_HC:
  8326. fHandledLPARAM = TRUE;
  8327. break;
  8328. default:
  8329. AssertSz(FALSE, "Unhandled LIST/LSUB operation");
  8330. break;
  8331. }
  8332. // Provide progress indication
  8333. if (SOT_SYNCING_STORE == m_sotCurrent && NULL != m_pCurrentCB)
  8334. {
  8335. // Update progress indication
  8336. m_pCurrentCB->OnProgress(m_sotCurrent, ++m_cFolders, 0, m_szAccountName);
  8337. }
  8338. exit:
  8339. IxpAssert(NULL == lParam || fHandledLPARAM || FAILED(hr));
  8340. if (fFreeLocalPath)
  8341. MemFree(pszLocalPath);
  8342. return S_OK;
  8343. }
  8344. //***************************************************************************
  8345. //***************************************************************************
  8346. HRESULT CIMAPSync::_OnAppendProgress(LPARAM lParam, DWORD dwCurrent, DWORD dwTotal)
  8347. {
  8348. APPEND_SEND_INFO *pAppendInfo = (APPEND_SEND_INFO *) lParam;
  8349. TraceCall("CIMAPSync::OnAppendProgress");
  8350. IxpAssert(m_cRef > 0);
  8351. IxpAssert(NULL != lParam);
  8352. IxpAssert(SOT_PUT_MESSAGE == m_sotCurrent);
  8353. if (NULL != m_pCurrentCB)
  8354. {
  8355. HRESULT hrTemp;
  8356. hrTemp = m_pCurrentCB->OnProgress(SOT_PUT_MESSAGE, dwCurrent, dwTotal, NULL);
  8357. TraceError(hrTemp);
  8358. }
  8359. return S_OK;
  8360. }
  8361. //***************************************************************************
  8362. //***************************************************************************
  8363. HRESULT CIMAPSync::_OnStatusResponse(IMAP_STATUS_RESPONSE *pisrStatusInfo)
  8364. {
  8365. HRESULT hrResult;
  8366. FOLDERID idFolder;
  8367. FOLDERINFO fiFolderInfo;
  8368. LPSTR pszMailboxName;
  8369. BOOL fValidPrefix;
  8370. LONG lMsgDelta;
  8371. LONG lUnreadDelta;
  8372. CHAR szInbox[CCHMAX_STRINGRES];
  8373. TraceCall("CIMAPSync::_OnStatusResponse");
  8374. IxpAssert(m_cRef > 0);
  8375. // Check that we have the data we need
  8376. if (NULL == pisrStatusInfo ||
  8377. NULL == pisrStatusInfo->pszMailboxName ||
  8378. '\0' == pisrStatusInfo->pszMailboxName[0] ||
  8379. FALSE == pisrStatusInfo->fMessages ||
  8380. FALSE == pisrStatusInfo->fUnseen)
  8381. {
  8382. hrResult = TraceResult(E_INVALIDARG);
  8383. goto exit;
  8384. }
  8385. // Figure out who this folder is (figure from path rather than module var FOLDERID for now)
  8386. // Assume m_cRootHierarchyChar is HC for this mbox, because IMAP doesn't return it
  8387. pszMailboxName = RemovePrefixFromPath(m_szRootFolderPrefix, pisrStatusInfo->pszMailboxName,
  8388. m_cRootHierarchyChar, &fValidPrefix, NULL);
  8389. AssertSz(fValidPrefix, "Foldercache can only select prefixed folders!");
  8390. // bobn, QFE, 7/9/99
  8391. // If we have the INBOX, we need to get the local name...
  8392. if(0 == StrCmpI(pszMailboxName, c_szINBOX))
  8393. {
  8394. LoadString(g_hLocRes, idsInbox, szInbox, ARRAYSIZE(szInbox));
  8395. pszMailboxName = szInbox;
  8396. }
  8397. hrResult = FindHierarchicalFolderName(pszMailboxName, m_cRootHierarchyChar,
  8398. &idFolder, pahfoDONT_CREATE_FOLDER);
  8399. if (FAILED(hrResult))
  8400. {
  8401. TraceResult(hrResult);
  8402. goto exit;
  8403. }
  8404. hrResult = m_pStore->GetFolderInfo(idFolder, &fiFolderInfo);
  8405. if (FAILED(hrResult))
  8406. {
  8407. TraceResult(hrResult);
  8408. goto exit;
  8409. }
  8410. // Calculate the number of messages and unread added by this STATUS response
  8411. Assert(sizeof(DWORD) == sizeof(LONG));
  8412. lMsgDelta = ((LONG)pisrStatusInfo->dwMessages) - ((LONG)fiFolderInfo.cMessages);
  8413. lUnreadDelta = ((LONG)pisrStatusInfo->dwUnseen) - ((LONG)fiFolderInfo.cUnread);
  8414. // If this is INBOX, we might just send a new mail notification
  8415. if (FOLDER_INBOX == fiFolderInfo.tySpecial && lUnreadDelta > 0 && NULL != m_pCurrentCB)
  8416. {
  8417. HRESULT hrTemp;
  8418. hrTemp = m_pCurrentCB->OnProgress(SOT_NEW_MAIL_NOTIFICATION, lUnreadDelta, 0, NULL);
  8419. TraceError(hrTemp);
  8420. }
  8421. // Update counts, and update delta so we can un-apply STATUS changes when re-syncing
  8422. fiFolderInfo.cMessages = pisrStatusInfo->dwMessages;
  8423. fiFolderInfo.cUnread = pisrStatusInfo->dwUnseen;
  8424. fiFolderInfo.dwStatusMsgDelta = ((LONG)fiFolderInfo.dwStatusMsgDelta) + lMsgDelta;
  8425. fiFolderInfo.dwStatusUnreadDelta = ((LONG)fiFolderInfo.dwStatusUnreadDelta) + lUnreadDelta;
  8426. hrResult = m_pStore->UpdateRecord(&fiFolderInfo);
  8427. m_pStore->FreeRecord(&fiFolderInfo);
  8428. if (FAILED(hrResult))
  8429. {
  8430. TraceResult(hrResult);
  8431. goto exit;
  8432. }
  8433. exit:
  8434. return hrResult;
  8435. }
  8436. //===========================================================================
  8437. // CRenameFolderInfo Class
  8438. //===========================================================================
  8439. // The CRenameFolderInfo class used to be a structure (much like
  8440. // AppendSendInfo). However, the RENAME operation was the first to
  8441. // stream IMAP commands without waiting for completion (no sequencing). This
  8442. // meant that if any errors occurred while IMAP commands were still in the
  8443. // air, the structure had to wait until the last command came back from the
  8444. // server. This was most easily done via AddRef/Release. A class was born.
  8445. //
  8446. // In the event that a send error occurred, this class is responsible for
  8447. // issuing the WM_IMAP_RENAMEDONE window message to the caller.
  8448. //***************************************************************************
  8449. // Function: CRenameFolderInfo (Constructor)
  8450. //***************************************************************************
  8451. CRenameFolderInfo::CRenameFolderInfo(void)
  8452. {
  8453. pszFullFolderPath = NULL;
  8454. cHierarchyChar = INVALID_HIERARCHY_CHAR;
  8455. pszNewFolderPath = NULL;
  8456. idRenameFolder = FOLDERID_INVALID;
  8457. iNumSubscribeRespExpected = 0;
  8458. iNumListRespExpected = 0;
  8459. iNumRenameRespExpected = 0;
  8460. iNumUnsubscribeRespExpected = 0;
  8461. iNumFailedSubs = 0;
  8462. iNumFailedUnsubs = 0;
  8463. fNonAtomicRename = 0;
  8464. pszRenameCmdOldFldrPath = NULL;
  8465. fPhaseOneSent = FALSE;
  8466. fPhaseTwoSent = FALSE;
  8467. hrLastError = S_OK;
  8468. pszProblem = NULL;
  8469. pszDetails = NULL;
  8470. m_lRefCount = 1;
  8471. } // CRenameFolderInfo;
  8472. //***************************************************************************
  8473. // Function: ~CRenameFolderInfo (Destructor)
  8474. //***************************************************************************
  8475. CRenameFolderInfo::~CRenameFolderInfo(void)
  8476. {
  8477. IxpAssert(0 == m_lRefCount);
  8478. MemFree(pszFullFolderPath);
  8479. MemFree(pszNewFolderPath);
  8480. MemFree(pszRenameCmdOldFldrPath);
  8481. SafeMemFree(pszProblem);
  8482. SafeMemFree(pszDetails);
  8483. } // ~CRenameFolderInfo
  8484. //***************************************************************************
  8485. // Function: AddRef (same one that you already know and love)
  8486. //***************************************************************************
  8487. long CRenameFolderInfo::AddRef(void)
  8488. {
  8489. IxpAssert(m_lRefCount > 0);
  8490. m_lRefCount += 1;
  8491. return m_lRefCount;
  8492. } // AddRef
  8493. //***************************************************************************
  8494. // Function: Release (same one that you already know and love)
  8495. //***************************************************************************
  8496. long CRenameFolderInfo::Release(void)
  8497. {
  8498. IxpAssert(m_lRefCount > 0);
  8499. m_lRefCount -= 1;
  8500. if (0 == m_lRefCount) {
  8501. delete this;
  8502. return 0;
  8503. }
  8504. else
  8505. return m_lRefCount;
  8506. } // Release
  8507. //***************************************************************************
  8508. //***************************************************************************
  8509. BOOL CRenameFolderInfo::IsDone(void)
  8510. {
  8511. if (m_lRefCount > 1)
  8512. return FALSE;
  8513. else
  8514. {
  8515. IxpAssert(1 == m_lRefCount);
  8516. return TRUE;
  8517. }
  8518. }
  8519. //***************************************************************************
  8520. //***************************************************************************
  8521. HRESULT CRenameFolderInfo::SetError(HRESULT hrResult, LPSTR pszProblemArg,
  8522. LPSTR pszDetailsArg)
  8523. {
  8524. HRESULT hr = S_OK;
  8525. TraceCall("CRenameFolderInfo::SetError");
  8526. IxpAssert(FAILED(hrResult));
  8527. hrLastError = hrResult;
  8528. SafeMemFree(pszProblem);
  8529. SafeMemFree(pszDetails);
  8530. if (NULL != pszProblemArg)
  8531. {
  8532. pszProblem = PszDupA(pszProblemArg);
  8533. if (NULL == pszProblem)
  8534. {
  8535. hr = TraceResult(E_OUTOFMEMORY);
  8536. goto exit;
  8537. }
  8538. }
  8539. if (NULL != pszDetailsArg)
  8540. {
  8541. pszDetails = PszDupA(pszDetailsArg);
  8542. if (NULL == pszDetails)
  8543. {
  8544. hr = TraceResult(E_OUTOFMEMORY);
  8545. goto exit;
  8546. }
  8547. }
  8548. exit:
  8549. return hr;
  8550. }