///////////////////////////////////////////////////////////////////////////// // Copyright (C) 1993-1998 Microsoft Corporation. All Rights Reserved. // // MODULE: newstask.cpp // // PURPOSE: Implements a task object to take care of news posts. // #include "pch.hxx" #include "resource.h" #include "imagelst.h" #include "storfldr.h" #include "mimeutil.h" #include "newstask.h" #include "goptions.h" #include "thormsgs.h" #include "spoolui.h" #include "xputil.h" #include "ourguid.h" #include "demand.h" #include "msgfldr.h" #include "taskutil.h" #include #include ASSERTDATA const static char c_szThis[] = "this"; static const PFNSTATEFUNC g_rgpfnState[NTS_MAX] = { NULL, // Idle NULL, // Connecting &CNewsTask::Post_Init, &CNewsTask::Post_NextMsg, NULL, // Post_OnResp &CNewsTask::Post_Dispose, &CNewsTask::Post_Done, &CNewsTask::NewMsg_Init, &CNewsTask::NewMsg_NextGroup, NULL, // NewMsg_OnResp &CNewsTask::NewMsg_HttpSyncStore, NULL, // NewMsg_OnHttpResp &CNewsTask::NewMsg_Done }; static const TCHAR c_szXNewsReader[] = "Microsoft Outlook Express " VER_PRODUCTVERSION_STRING; // // FUNCTION: CNewsTask::CNewsTask() // // PURPOSE: Initializes the member variables of the object. // CNewsTask::CNewsTask() { m_cRef = 1; m_fInited = FALSE; m_dwFlags = 0; m_state = NTS_IDLE; m_eidCur = 0; m_pInfo = NULL; m_fConnectFailed = FALSE; m_szAccount[0] = 0; m_szAccountId[0] = 0; m_idAccount = FOLDERID_INVALID; m_pAccount = NULL; m_cEvents = 0; m_fCancel = FALSE; m_pBindCtx = NULL; m_pUI = NULL; m_pServer = NULL; m_pOutbox = NULL; m_pSent = NULL; m_hwnd = 0; m_cMsgsPost = 0; m_cCurPost = 0; m_cFailed = 0; m_cCurParts = 0; m_cPartsCompleted = 0; m_fPartFailed = FALSE; m_rgMsgInfo = NULL; m_pSplitInfo = NULL; m_cGroups = 0; m_cCurGroup = -1; m_rgidGroups = NULL; m_dwNewInboxMsgs = 0; m_pCancel = NULL; m_hTimeout = NULL; m_tyOperation = SOT_INVALID; } // // FUNCTION: CNewsTask::~CNewsTask() // // PURPOSE: Frees any resources allocated during the life of the class. // CNewsTask::~CNewsTask() { DestroyWindow(m_hwnd); FreeSplitInfo(); SafeMemFree(m_pInfo); SafeRelease(m_pBindCtx); SafeRelease(m_pUI); SafeRelease(m_pAccount); if (m_pServer) { m_pServer->Close(MSGSVRF_HANDS_OFF_SERVER); m_pServer->Release(); } Assert(NULL == m_pOutbox); Assert(NULL == m_pSent); SafeMemFree(m_rgMsgInfo); CallbackCloseTimeout(&m_hTimeout); SafeRelease(m_pCancel); } HRESULT CNewsTask::QueryInterface(REFIID riid, LPVOID FAR* ppvObj) { if (NULL == *ppvObj) return (E_INVALIDARG); *ppvObj = NULL; if (IsEqualIID(riid, IID_IUnknown)) *ppvObj = (LPVOID)(ISpoolerTask *) this; else if (IsEqualIID(riid, IID_ISpoolerTask)) *ppvObj = (LPVOID)(ISpoolerTask *) this; else if (IsEqualIID(riid, IID_IStoreCallback)) *ppvObj = (LPVOID)(IStoreCallback *) this; else if (IsEqualIID(riid, IID_ITimeoutCallback)) *ppvObj = (LPVOID)(ITimeoutCallback *) this; if (NULL == *ppvObj) return (E_NOINTERFACE); AddRef(); return (S_OK); } ULONG CNewsTask::AddRef(void) { ULONG cRefT; cRefT = ++m_cRef; return (cRefT); } ULONG CNewsTask::Release(void) { ULONG cRefT; cRefT = --m_cRef; if (0 == cRefT) delete this; return (cRefT); } static const char c_szNewsTask[] = "News Task"; // // FUNCTION: CNewsTask::Init() // // PURPOSE: Called by the spooler engine to tell us what type of task to // execute and to provide us with a pointer to our bind context. // // PARAMETERS: // dwFlags - Flags to tell us what types of things to do // pBindCtx - Pointer to the bind context interface we are to use // // RETURN VALUE: // E_INVALIDARG // SP_E_ALREADYINITIALIZED // S_OK // E_OUTOFMEMORY // HRESULT CNewsTask::Init(DWORD dwFlags, ISpoolerBindContext *pBindCtx) { HRESULT hr = S_OK; // Validate the arguments if (NULL == pBindCtx) return (E_INVALIDARG); // Check to see if we've been initialzed already if (m_fInited) { hr = SP_E_ALREADYINITIALIZED; goto exit; } // Copy the flags m_dwFlags = dwFlags; // Copy the bind context pointer m_pBindCtx = pBindCtx; m_pBindCtx->AddRef(); // Create the window WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); if (!GetClassInfoEx(g_hInst, c_szNewsTask, &wc)) { wc.style = 0; wc.lpfnWndProc = TaskWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = g_hInst; wc.hCursor = NULL; wc.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1); wc.lpszMenuName = NULL; wc.lpszClassName = c_szNewsTask; wc.hIcon = NULL; wc.hIconSm = NULL; RegisterClassEx(&wc); } m_hwnd = CreateWindow(c_szNewsTask, NULL, WS_POPUP, 10, 10, 10, 10, GetDesktopWindow(), NULL, g_hInst, this); if (!m_hwnd) { hr = E_OUTOFMEMORY; goto exit; } m_fInited = TRUE; exit: return (hr); } // // FUNCTION: CNewsTask::BuildEvents() // // PURPOSE: This method is called by the spooler engine telling us to create // and event list for the account specified. // // PARAMETERS: // pAccount - Account object to build the event list for // // RETURN VALUE: // SP_E_UNINITALIZED // E_INVALIDARG // S_OK // HRESULT CNewsTask::BuildEvents(ISpoolerUI *pSpoolerUI, IImnAccount *pAccount, FOLDERID idFolder) { HRESULT hr; BOOL fIMAP; BOOL fHttp; FOLDERINFO fiFolderInfo; DWORD dw = 0; ULARGE_INTEGER uhLastFileTime64 = {0}; ULARGE_INTEGER uhCurFileTime64 = {0}; ULARGE_INTEGER uhMinPollingInterval64 = {0}; FILETIME CurFileTime = {0}; DWORD cb = 0; Assert(pAccount != NULL); Assert(pSpoolerUI != NULL); Assert(m_fInited); m_pAccount = pAccount; m_pAccount->AddRef(); // Get the account name from the account object if (FAILED(hr = pAccount->GetPropSz(AP_ACCOUNT_NAME, m_szAccount, ARRAYSIZE(m_szAccount)))) return(hr); // Get the account name from the account object if (FAILED(hr = pAccount->GetPropSz(AP_ACCOUNT_ID, m_szAccountId, ARRAYSIZE(m_szAccountId)))) return(hr); if (FAILED(hr = g_pStore->FindServerId(m_szAccountId, &m_idAccount))) return(hr); // Copy the UI object m_pUI = pSpoolerUI; m_pUI->AddRef(); // Create the server object hr = g_pStore->GetFolderInfo(m_idAccount, &fiFolderInfo); if (FAILED(hr)) return(hr); fIMAP = (fiFolderInfo.tyFolder == FOLDER_IMAP); fHttp = (fiFolderInfo.tyFolder == FOLDER_HTTPMAIL); hr = CreateMessageServerType(fiFolderInfo.tyFolder, &m_pServer); g_pStore->FreeRecord(&fiFolderInfo); if (FAILED(hr)) return(hr); hr = m_pServer->Initialize(g_pLocalStore, m_idAccount, NULL, FOLDERID_INVALID); if (FAILED(hr)) return(hr); if (!fIMAP & !fHttp) { // Add posts to upload if (DELIVER_SEND & m_dwFlags) InsertOutbox(m_szAccountId, pAccount); } if (fHttp) { if (!!(m_dwFlags & DELIVER_AT_INTERVALS)) { //If this is background polling make sure that HTTP's maxpolling interval has elapsed before //polling again. cb = sizeof(uhMinPollingInterval64); IF_FAILEXIT(hr = pAccount->GetProp(AP_HTTPMAIL_MINPOLLINGINTERVAL, (LPBYTE)&uhMinPollingInterval64, &cb)); cb = sizeof(uhLastFileTime64); IF_FAILEXIT(hr = pAccount->GetProp(AP_HTTPMAIL_LASTPOLLEDTIME, (LPBYTE)&uhLastFileTime64, &cb)); GetSystemTimeAsFileTime(&CurFileTime); uhCurFileTime64.QuadPart = CurFileTime.dwHighDateTime; uhCurFileTime64.QuadPart = uhCurFileTime64.QuadPart << 32; uhCurFileTime64.QuadPart += CurFileTime.dwLowDateTime; //We do not want to do background polling if the last time we polled this http mail //account was less than maximum polling interval specified by the server. //We should only poll if the time elapsed is greater than or equal to the max polling interval if ((uhCurFileTime64.QuadPart - uhLastFileTime64.QuadPart) < uhMinPollingInterval64.QuadPart) { goto exit; } //Mark the last polled time. hr = pAccount->SetProp(AP_HTTPMAIL_LASTPOLLEDTIME, (LPBYTE)&uhCurFileTime64, sizeof(uhCurFileTime64)); } } // Check for new msgs if ((DELIVER_POLL & m_dwFlags) && (fIMAP || fHttp || !(m_dwFlags & DELIVER_NO_NEWSPOLL))) { if (ISFLAGSET(m_dwFlags, DELIVER_NOSKIP) || (!fIMAP && !fHttp && (FAILED(pAccount->GetPropDw(AP_NNTP_POLL, &dw)) || dw != 0)) || (fIMAP && (FAILED(pAccount->GetPropDw(AP_IMAP_POLL, &dw)) || dw != 0)) || (fHttp && (FAILED(pAccount->GetPropDw(AP_HTTPMAIL_POLL, &dw)) || dw != 0))) { InsertNewMsgs(m_szAccountId, pAccount, fHttp); } } exit: return (hr); } // // FUNCTION: CNewsTask::Execute() // // PURPOSE: This signals our task to start executing an event. // // PARAMETERS: // pSpoolerUI - Pointer of the UI object we'll display progress through // eid - ID of the event to execute // dwTwinkie - Our extra information we associated with the event // // RETURN VALUE: // SP_E_EXECUTING // S_OK // E_INVALIDARG // SP_E_UNINITIALIZED // HRESULT CNewsTask::Execute(EVENTID eid, DWORD_PTR dwTwinkie) { TCHAR szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES]; // Make sure we're already idle Assert(m_state == NTS_IDLE); // Make sure we're initialized Assert(m_fInited); // Copy the event id and event info m_eidCur = eid; m_pInfo = (EVENTINFO *) dwTwinkie; // Update the event UI to an executing state Assert(m_pUI); m_pUI->SetProgressRange(1); // Set up the progress AthLoadString(idsInetMailConnectingHost, szRes, ARRAYSIZE(szRes)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, m_szAccount); m_pUI->SetGeneralProgress(szBuf); m_pUI->SetAnimation(idanDownloadNews, TRUE); // Depending on the type of event, set the state machine info switch (((EVENTINFO*) dwTwinkie)->type) { case EVENT_OUTBOX: m_state = NTS_POST_INIT; break; case EVENT_NEWMSGS: m_state = NTS_NEWMSG_INIT; break; } PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); return(S_OK); } HRESULT CNewsTask::CancelEvent(EVENTID eid, DWORD_PTR dwTwinkie) { // Make sure we're initialized Assert(m_fInited); Assert(dwTwinkie != 0); MemFree((EVENTINFO *)dwTwinkie); return(S_OK); } // // FUNCTION: CNewsTask::ShowProperties // // PURPOSE: // // PARAMETERS: // // // RETURN VALUE: // // // COMMENTS: // // HRESULT CNewsTask::ShowProperties(HWND hwndParent, EVENTID eid, DWORD_PTR dwTwinkie) { return (E_NOTIMPL); } // // FUNCTION: CNewsTask::GetExtendedDetails // // PURPOSE: // // PARAMETERS: // // // RETURN VALUE: // // // COMMENTS: // // HRESULT CNewsTask::GetExtendedDetails(EVENTID eid, DWORD_PTR dwTwinkie, LPSTR *ppszDetails) { return (E_NOTIMPL); } // // FUNCTION: CNewsTask::Cancel // // PURPOSE: // // PARAMETERS: // // // RETURN VALUE: // // // COMMENTS: // // HRESULT CNewsTask::Cancel(void) { // this can happen if user cancels out of connect dlg if (m_state == NTS_IDLE) return(S_OK); // Drop the server connection if (m_pServer) m_pServer->Close(MSGSVRF_DROP_CONNECTION); m_fCancel = TRUE; if (m_pInfo->type == EVENT_OUTBOX) m_state = NTS_POST_END; else m_state = NTS_NEWMSG_END; PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); return (S_OK); } // // FUNCTION: CNewsTask::InsertOutbox() // // PURPOSE: Checks the outbox for news posts destine for this news account. // // PARAMETERS: // pszAcctId - ID of the account to check the outbox for. // // RETURN VALUE: // E_UNEXECTED // E_OUTOFMEMORY // S_OK // HRESULT CNewsTask::InsertOutbox(LPTSTR pszAcctId, IImnAccount *pAccount) { HRESULT hr = S_OK; IMessageFolder *pOutbox = NULL; MESSAGEINFO MsgInfo={0}; HROWSET hRowset=NULL; // Get the outbox if (FAILED(hr = m_pBindCtx->BindToObject(IID_CLocalStoreOutbox, (LPVOID *) &pOutbox))) goto exit; // Loop through the outbox looking for posts to this server m_cMsgsPost = 0; // Create a Rowset if (FAILED(hr = pOutbox->CreateRowset(IINDEX_PRIMARY, 0, &hRowset))) goto exit; // Get the first message while (S_OK == pOutbox->QueryRowset(hRowset, 1, (void **)&MsgInfo, NULL)) { // Has this message been submitted and is it a news message? if ((MsgInfo.dwFlags & (ARF_SUBMITTED | ARF_NEWSMSG)) == (ARF_SUBMITTED | ARF_NEWSMSG)) { // Is the account the same as the account we're looking for if (MsgInfo.pszAcctId && 0 == lstrcmpi(MsgInfo.pszAcctId, pszAcctId)) m_cMsgsPost++; } // Free the header info pOutbox->FreeRecord(&MsgInfo); } // Release Lock pOutbox->CloseRowset(&hRowset); // Good to go hr = S_OK; // If there were any messages then add the event if (m_cMsgsPost) { EVENTINFO *pei = NULL; TCHAR szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES]; EVENTID eid; // Allocate a structure to set as our cookie if (!MemAlloc((LPVOID*) &pei, sizeof(EVENTINFO))) { hr = E_OUTOFMEMORY; goto exit; } // Fill out the event info pei->szGroup[0] = 0; pei->type = EVENT_OUTBOX; // Create the event description AthLoadString(idsNewsTaskPost, szRes, ARRAYSIZE(szRes)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, m_cMsgsPost, m_szAccount); // Insert the event into the spooler hr = m_pBindCtx->RegisterEvent(szBuf, this, (DWORD_PTR) pei, pAccount, &eid); if (FAILED(hr)) goto exit; m_cEvents++; } // if (m_cMsgsPost) exit: // Release Lock if (pOutbox) pOutbox->CloseRowset(&hRowset); SafeRelease(pOutbox); return (hr); } // // FUNCTION: CNewsTask::TaskWndProc() // // PURPOSE: Hidden window that processes messages for this task. // LRESULT CALLBACK CNewsTask::TaskWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CNewsTask *pThis = (CNewsTask *) GetProp(hwnd, c_szThis); switch (uMsg) { case WM_CREATE: { LPCREATESTRUCT pcs = (LPCREATESTRUCT) lParam; pThis = (CNewsTask *) pcs->lpCreateParams; SetProp(hwnd, c_szThis, (LPVOID) pThis); return (0); } case NTM_NEXTSTATE: if (pThis) { pThis->AddRef(); pThis->NextState(); pThis->Release(); } return (0); case WM_DESTROY: RemoveProp(hwnd, c_szThis); break; } return (DefWindowProc(hwnd, uMsg, wParam, lParam)); } // // FUNCTION: CNewsTask::Post_Init() // // PURPOSE: Called when we're in a NTS_POST_INIT state. The task is // initialized to execute the posting event. // // RETURN VALUE: // E_OUTOFMEMORY // E_UNEXPECTED // S_OK // HRESULT CNewsTask::Post_Init(void) { HRESULT hr = S_OK; DWORD dwCur = 0; MESSAGEINFO MsgInfo={0}; HROWSET hRowset=NULL; BOOL fInserted = FALSE; TCHAR *pszAcctName = NULL; // Open the outbox Assert(m_pBindCtx); if (FAILED(hr = m_pBindCtx->BindToObject(IID_CLocalStoreOutbox, (LPVOID *) &m_pOutbox))) goto exit; Assert(m_pSent == NULL); // If we use sent items, get that pointer too if (DwGetOption(OPT_SAVESENTMSGS)) { if (FAILED(hr = TaskUtil_OpenSentItemsFolder(m_pAccount, &m_pSent))) goto exit; Assert(m_pSent != NULL); } // Allocate an array of header pointers for the messages we're going to post if (!MemAlloc((LPVOID*) &m_rgMsgInfo, m_cMsgsPost * sizeof(MESSAGEINFO))) { hr = E_OUTOFMEMORY; goto exit; } // Zero out the array ZeroMemory(m_rgMsgInfo, m_cMsgsPost * sizeof(MESSAGEINFO)); // Create Rowset if (FAILED(hr = m_pOutbox->CreateRowset(IINDEX_PRIMARY, 0, &hRowset))) goto exit; // While we have stuff while (S_OK == m_pOutbox->QueryRowset(hRowset, 1, (void **)&MsgInfo, NULL)) { // Has this message been submitted and is it a news message? if ((MsgInfo.dwFlags & (ARF_SUBMITTED | ARF_NEWSMSG)) == (ARF_SUBMITTED | ARF_NEWSMSG)) { // Is the account the same as the account we're looking for if (MsgInfo.pszAcctId && 0 == lstrcmpi(MsgInfo.pszAcctId, m_szAccountId)) { if (NULL == pszAcctName && MsgInfo.pszAcctName) pszAcctName = PszDup(MsgInfo.pszAcctName); CopyMemory(&m_rgMsgInfo[dwCur++], &MsgInfo, sizeof(MESSAGEINFO)); ZeroMemory(&MsgInfo, sizeof(MESSAGEINFO)); } } if (dwCur >= (DWORD)m_cMsgsPost) { Assert(0); break; } // Free the header info m_pOutbox->FreeRecord(&MsgInfo); } // Release Lock m_pOutbox->CloseRowset(&hRowset); // Good to go hr = S_OK; //Assert(dwCur); // Update the UI to an executing state Assert(m_pUI); // Set up the progress TCHAR szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES]; AthLoadString(idsProgDLPostTo, szRes, ARRAYSIZE(szRes)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, (LPTSTR) pszAcctName ? pszAcctName : ""); Assert(m_pUI); m_pUI->SetGeneralProgress(szBuf); m_pUI->SetProgressRange((WORD) m_cMsgsPost); m_pUI->SetAnimation(idanOutbox, TRUE); m_pBindCtx->Notify(DELIVERY_NOTIFY_SENDING_NEWS, 0); // Reset the counter to post the first message m_cCurPost = -1; m_cFailed = 0; m_state = NTS_POST_NEXT; exit: SafeMemFree(pszAcctName); if (m_pOutbox) m_pOutbox->CloseRowset(&hRowset); // If something failed, then update the UI if (FAILED(hr)) { m_pUI->InsertError(m_eidCur, MAKEINTRESOURCE(idshrCantOpenOutbox)); m_cFailed = m_cMsgsPost; // Move to a terminating state m_state = NTS_POST_END; } PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); return (hr); } void CNewsTask::FreeSplitInfo() { if (m_pSplitInfo != NULL) { if (m_pSplitInfo->pEnumParts != NULL) m_pSplitInfo->pEnumParts->Release(); if (m_pSplitInfo->pMsgParts != NULL) m_pSplitInfo->pMsgParts->Release(); MemFree(m_pSplitInfo); m_pSplitInfo = NULL; } } HRESULT CNewsTask::Post_NextPart(void) { LPMIMEMESSAGE pMsgSplit; HRESULT hr; LPSTREAM pStream; LPMESSAGEINFO pInfo; DWORD dwLines; char rgch[12]; PROPVARIANT rUserData; Assert(m_pSplitInfo->pEnumParts != NULL); pInfo = &m_rgMsgInfo[m_cCurPost]; hr = m_pSplitInfo->pEnumParts->Next(1, &pMsgSplit, NULL); if (hr == S_OK) { Assert(pMsgSplit); rUserData.vt = VT_LPSTR; rUserData.pszVal = (LPSTR)pInfo->pszAcctName; pMsgSplit->SetProp(STR_ATT_ACCOUNTNAME, 0, &rUserData); rUserData.pszVal = pInfo->pszAcctId;; pMsgSplit->SetProp(PIDTOSTR(PID_ATT_ACCOUNTID), 0, &rUserData); // since this is a new message it doesn't have a line // count yet, so we need to do it before we stick it // in the outbox HrComputeLineCount(pMsgSplit, &dwLines); wnsprintf(rgch, ARRAYSIZE(rgch), "%d", dwLines); MimeOleSetBodyPropA(pMsgSplit, HBODY_ROOT, PIDTOSTR(PID_HDR_LINES), NOFLAGS, rgch); MimeOleSetBodyPropA(pMsgSplit, HBODY_ROOT, PIDTOSTR(PID_HDR_XNEWSRDR), NOFLAGS, c_szXNewsReader); // Final Parameter: fSaveChange = TRUE since messsage is dirty hr = pMsgSplit->GetMessageSource(&pStream, 0); if (SUCCEEDED(hr) && pStream != NULL) { hr = m_pServer->PutMessage(m_pSplitInfo->idFolder, pInfo->dwFlags, &pInfo->ftReceived, pStream, this); m_cCurParts++; pStream->Release(); } pMsgSplit->Release(); } return(hr); } // // FUNCTION: CNewsTask::Post_NextMsg() // // PURPOSE: Posts the next message in our outbox. // // RETURN VALUE: // // HRESULT CNewsTask::Post_NextMsg(void) { LPMESSAGEINFO pInfo; FOLDERID idFolder; char szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES]; DWORD dw, cbSize, cbMaxSendMsgSize; IImnAccount *pAcct; LPMIMEMESSAGE pMsg = 0; HRESULT hr = S_OK; IStream *pStream = NULL; if (m_pSplitInfo != NULL) { hr = Post_NextPart(); Assert(hr != S_OK); if (hr == E_PENDING) { m_state = NTS_POST_RESP; return(S_OK); } FreeSplitInfo(); if (FAILED(hr)) { m_cFailed++; m_fPartFailed = TRUE; } Assert(m_pUI); m_pUI->IncrementProgress(1); PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); return(S_OK); } m_cCurPost++; m_cCurParts = 0; m_cPartsCompleted = 0; m_fPartFailed = FALSE; // Check to see if we're already done if (m_cCurPost >= m_cMsgsPost) { // If so, move to a cleanup state m_state = NTS_POST_END; PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); return(S_OK); } // Update the progress UI AthLoadString(idsProgDLPost, szRes, ARRAYSIZE(szRes)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, m_cCurPost + 1, m_cMsgsPost); Assert(m_pUI); m_pUI->SetSpecificProgress(szBuf); pInfo = &m_rgMsgInfo[m_cCurPost]; // Load the message stream from the store if (SUCCEEDED(hr = m_pOutbox->OpenMessage(pInfo->idMessage, OPEN_MESSAGE_SECURE, &pMsg, this))) { hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, pInfo->pszAcctId, &pAcct); if (SUCCEEDED(hr)) { hr = g_pStore->FindServerId(pInfo->pszAcctId, &idFolder); if (SUCCEEDED(hr)) { if (SUCCEEDED(pAcct->GetPropDw(AP_NNTP_SPLIT_MESSAGES, &dw)) && dw != 0 && SUCCEEDED(pAcct->GetPropDw(AP_NNTP_SPLIT_SIZE, &dw))) { cbMaxSendMsgSize = dw; } else { cbMaxSendMsgSize = 0xffffffff; } SideAssert(pMsg->GetMessageSize(&cbSize, 0)==S_OK); if (cbSize < (cbMaxSendMsgSize * 1024)) { hr = pMsg->GetMessageSource(&pStream, 0); if (SUCCEEDED(hr) && pStream != NULL) { hr = m_pServer->PutMessage(idFolder, pInfo->dwFlags, &pInfo->ftReceived, pStream, this); m_cCurParts ++; pStream->Release(); } } else { Assert(m_pSplitInfo == NULL); if (!MemAlloc((void **)&m_pSplitInfo, sizeof(SPLITMSGINFO))) { hr = E_OUTOFMEMORY; } else { ZeroMemory(m_pSplitInfo, sizeof(SPLITMSGINFO)); m_pSplitInfo->idFolder = idFolder; hr = pMsg->SplitMessage(cbMaxSendMsgSize * 1024, &m_pSplitInfo->pMsgParts); if (hr == S_OK) { hr = m_pSplitInfo->pMsgParts->EnumParts(&m_pSplitInfo->pEnumParts); if (hr == S_OK) { hr = Post_NextPart(); } } if (hr != E_PENDING) FreeSplitInfo(); } } } pAcct->Release(); } if (hr == E_PENDING) { m_state = NTS_POST_RESP; hr = S_OK; goto exit; } } // If we get here, something failed. m_cFailed++; Assert(m_pUI); m_pUI->IncrementProgress(1); PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); exit: SafeRelease(pMsg); return (hr); } HRESULT CNewsTask::Post_Dispose() { HRESULT hr; hr = DisposeOfPosting(m_rgMsgInfo[m_cCurPost].idMessage); if (hr == E_PENDING) return(S_OK); // TODO: handle error // Update the progress bar Assert(m_pUI); m_pUI->IncrementProgress(1); // Move on to the next post m_state = NTS_POST_NEXT; PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); return(hr); } // // FUNCTION: CNewsTask::Post_Done() // // PURPOSE: Allows the posting event to clean up and finalize the UI. // // RETURN VALUE: // S_OK // HRESULT CNewsTask::Post_Done(void) { // Free the header array if (m_rgMsgInfo && m_cMsgsPost) { for (LONG i=0; iFreeRecord(&m_rgMsgInfo[i]); MemFree(m_rgMsgInfo); } // Free the folder pointers we're hanging on to SafeRelease(m_pOutbox); SafeRelease(m_pSent); // Tell the spooler we're done Assert(m_pBindCtx); m_pBindCtx->Notify(DELIVERY_NOTIFY_COMPLETE, m_dwNewInboxMsgs); if (m_fCancel) { m_pBindCtx->EventDone(m_eidCur, EVENT_CANCELED); m_fCancel = FALSE; } else if (m_cFailed == m_cMsgsPost) m_pBindCtx->EventDone(m_eidCur, EVENT_FAILED); else if (m_cFailed == 0) m_pBindCtx->EventDone(m_eidCur, EVENT_SUCCEEDED); else m_pBindCtx->EventDone(m_eidCur, EVENT_WARNINGS); m_cMsgsPost = 0; m_cCurPost = 0; m_cFailed = 0; m_rgMsgInfo = NULL; SafeMemFree(m_pInfo); m_eidCur = 0; m_cEvents--; if (m_cEvents == 0 && m_pServer) m_pServer->Close(MSGSVRF_DROP_CONNECTION); m_state = NTS_IDLE; return (S_OK); } HRESULT CNewsTask::DisposeOfPosting(MESSAGEID dwMsgID) { MESSAGEIDLIST MsgIdList; ADJUSTFLAGS AdjustFlags; HRESULT hrResult = E_FAIL; MsgIdList.cAllocated = 0; MsgIdList.cMsgs = 1; MsgIdList.prgidMsg = &dwMsgID; if (DwGetOption(OPT_SAVESENTMSGS)) { // If we've reached this point, it's time to try local Sent Items folder Assert(m_pSent != NULL); // change msg flags first, so if copy fails, the user doesn't get // messed up by us posting the message every time they do a send AdjustFlags.dwRemove = ARF_SUBMITTED | ARF_UNSENT; AdjustFlags.dwAdd = ARF_READ; hrResult = m_pOutbox->SetMessageFlags(&MsgIdList, &AdjustFlags, NULL, NULL); Assert(hrResult != E_PENDING); if (SUCCEEDED(hrResult)) hrResult = m_pOutbox->CopyMessages(m_pSent, COPY_MESSAGE_MOVE, &MsgIdList, NULL, NULL, this); } else { // If we've reached this point, it's time to delete the message from the Outbox hrResult = m_pOutbox->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, &MsgIdList, NULL, this); } return hrResult; } // // FUNCTION: CNewsTask::NextState() // // PURPOSE: Executes the function for the current state // void CNewsTask::NextState(void) { if (NULL != g_rgpfnState[m_state]) (this->*(g_rgpfnState[m_state]))(); } HRESULT CNewsTask::InsertNewMsgs(LPSTR pszAccountId, IImnAccount *pAccount, BOOL fHttp) { HRESULT hr = S_OK; ULONG cSub = 0; IEnumerateFolders *pEnum = NULL; if (fHttp) m_cGroups = 1; else { // Load the sublist for this server Assert(m_idAccount != FOLDERID_INVALID); hr = g_pStore->EnumChildren(m_idAccount, TRUE, &pEnum); if (FAILED(hr)) goto exit; hr = pEnum->Count(&cSub); if (FAILED(hr)) goto exit; m_cGroups = (int)cSub; } // If there were any groups then add the event if (m_cGroups) { EVENTINFO *pei; char szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES]; EVENTID eid; // Allocate a structure to set as our cookie if (!MemAlloc((LPVOID*) &pei, sizeof(EVENTINFO))) { hr = E_OUTOFMEMORY; goto exit; } // Fill out the event info pei->szGroup[0] = 0; pei->type = EVENT_NEWMSGS; // Create the event description AthLoadString(idsCheckNewMsgsServer, szRes, ARRAYSIZE(szRes)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, m_szAccount); // Insert the event into the spooler hr = m_pBindCtx->RegisterEvent(szBuf, this, (DWORD_PTR) pei, pAccount, &eid); m_cEvents++; } exit: SafeRelease(pEnum); return (hr); } HRESULT CNewsTask::NewMsg_InitHttp(void) { HRESULT hr = S_OK; // Set up the progress TCHAR szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES]; AthLoadString(IDS_SPS_POP3CHECKING, szRes, ARRAYSIZE(szRes)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, (LPTSTR) m_szAccount); Assert(m_pUI); m_pUI->SetGeneralProgress(szBuf); m_pUI->SetProgressRange(1); m_pUI->SetAnimation(idanInbox, TRUE); m_pBindCtx->Notify(DELIVERY_NOTIFY_CHECKING, 0); // set the to generate correct success/failure message m_cGroups = 1; m_state = NTS_NEWMSG_HTTPSYNCSTORE; return hr; } HRESULT CNewsTask::NewMsg_Init(void) { const BOOL fDONT_INCLUDE_PARENT = FALSE; const BOOL fSUBSCRIBED_ONLY = TRUE; FOLDERINFO FolderInfo = {0}; HRESULT hr = S_OK; DWORD dwAllocated; DWORD dwUsed; BOOL fImap = FALSE; DWORD dwIncludeAll = 0; DWORD dwDone = FALSE; Assert(m_idAccount != FOLDERID_INVALID); if (SUCCEEDED(g_pStore->GetFolderInfo(m_idAccount, &FolderInfo))) { // httpmail updates folder counts differently if (FOLDER_HTTPMAIL == FolderInfo.tyFolder) { g_pStore->FreeRecord(&FolderInfo); IF_FAILEXIT(hr = m_pAccount->GetPropDw(AP_HTTPMAIL_GOTPOLLINGINTERVAL, &dwDone)); if (!dwDone) { //We need to get the polling interval from the server //This is an asynchrnous call. The value gets updated in OnComplete. //Meanwhile we go ahead and poll for new messages hr = m_pServer->GetMinPollingInterval((IStoreCallback*)this); } hr = NewMsg_InitHttp(); goto exit; } fImap = (FolderInfo.tyFolder == FOLDER_IMAP); if (fImap) { if (FAILED(hr = m_pAccount->GetPropDw(AP_IMAP_POLL_ALL_FOLDERS, &dwIncludeAll))) { dwIncludeAll = 0; } } g_pStore->FreeRecord(&FolderInfo); } if (fImap && (!dwIncludeAll)) { dwUsed = 0; if (FAILED(GetInboxId(g_pStore, m_idAccount, &m_rgidGroups, &dwUsed))) goto exit; } else { // Get an array of all subscribed folders hr = FlattenHierarchy(g_pStore, m_idAccount, fDONT_INCLUDE_PARENT, fSUBSCRIBED_ONLY, &m_rgidGroups, &dwAllocated, &dwUsed); if (FAILED(hr)) { TraceResult(hr); goto exit; } } m_cGroups = dwUsed; // Set up the progress TCHAR szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES]; AthLoadString(IDS_SPS_POP3CHECKING, szRes, ARRAYSIZE(szRes)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, (LPTSTR) m_szAccount); Assert(m_pUI); m_pUI->SetGeneralProgress(szBuf); m_pUI->SetProgressRange((WORD) m_cGroups); if (fImap) { m_pUI->SetAnimation(idanInbox, TRUE); m_pBindCtx->Notify(DELIVERY_NOTIFY_CHECKING, 0); } else { m_pUI->SetAnimation(idanDownloadNews, TRUE); m_pBindCtx->Notify(DELIVERY_NOTIFY_CHECKING_NEWS, 0); } // Reset the counters for the first group m_cCurGroup = -1; m_cFailed = 0; m_state = NTS_NEWMSG_NEXTGROUP; exit: // If something failed, update the UI if (FAILED(hr)) { m_pUI->InsertError(m_eidCur, MAKEINTRESOURCE(idsErrNewMsgsFailed)); m_cFailed = m_cGroups; // Move to a terminating state m_state = NTS_NEWMSG_END; } PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); return (hr); } HRESULT CNewsTask::NewMsg_NextGroup(void) { HRESULT hr = E_FAIL; do { FOLDERINFO info; // Keep looping until we find a folder that's selectable and exists m_cCurGroup++; // Check to see if we're already done if (m_cCurGroup >= m_cGroups) { m_state = NTS_NEWMSG_END; hr = S_OK; PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); break; } if (SUCCEEDED(hr = g_pStore->GetFolderInfo(m_rgidGroups[m_cCurGroup], &info))) { if (0 == (info.dwFlags & (FOLDER_NOSELECT | FOLDER_NONEXISTENT))) { // Update the progress UI TCHAR szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES]; AthLoadString(idsLogCheckingNewMessages, szRes, ARRAYSIZE(szRes)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, info.pszName); g_pStore->FreeRecord(&info); Assert(m_pUI); m_pUI->SetSpecificProgress(szBuf); // Send the group command to the server if (E_PENDING == (hr = m_pServer->GetFolderCounts(m_rgidGroups[m_cCurGroup], (IStoreCallback *)this))) { m_state = NTS_NEWMSG_RESP; hr = S_OK; } break; } else { g_pStore->FreeRecord(&info); } } } while (1); if (FAILED(hr)) { // If we get here, something failed m_cFailed++; Assert(m_pUI); m_pUI->IncrementProgress(1); PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); } return (hr); } HRESULT CNewsTask::NewMsg_HttpSyncStore(void) { HRESULT hr = S_OK; // send the command to the server hr = m_pServer->SynchronizeStore(FOLDERID_INVALID, NOFLAGS, (IStoreCallback *)this); if (E_PENDING == hr) { m_state = NTS_NEWMSG_HTTPRESP; hr = S_OK; } return hr; } HRESULT CNewsTask::NewMsg_Done(void) { HRESULT hr = S_OK; // Free the group array if (m_rgidGroups) { MemFree(m_rgidGroups); m_rgidGroups = NULL; } SafeMemFree(m_pInfo); // Tell the spooler we're done Assert(m_pBindCtx); m_pBindCtx->Notify(DELIVERY_NOTIFY_COMPLETE, m_dwNewInboxMsgs); if (m_fCancel) { m_pBindCtx->EventDone(m_eidCur, EVENT_CANCELED); m_fCancel = FALSE; } else if (m_cFailed == m_cGroups) m_pBindCtx->EventDone(m_eidCur, EVENT_FAILED); else if (m_cFailed == 0) m_pBindCtx->EventDone(m_eidCur, EVENT_SUCCEEDED); else m_pBindCtx->EventDone(m_eidCur, EVENT_WARNINGS); m_cGroups = 0; m_cCurGroup = 0; m_cFailed = 0; m_dwNewInboxMsgs = 0; m_eidCur = 0; m_cEvents--; if (m_cEvents == 0 && m_pServer) m_pServer->Close(MSGSVRF_DROP_CONNECTION); m_state = NTS_IDLE; return (S_OK); } // -------------------------------------------------------------------------------- // CNewsTask::IsDialogMessage // -------------------------------------------------------------------------------- STDMETHODIMP CNewsTask::IsDialogMessage(LPMSG pMsg) { return S_FALSE; } STDMETHODIMP CNewsTask::OnFlagsChanged(DWORD dwFlags) { m_dwFlags = dwFlags; return (S_OK); } STDMETHODIMP CNewsTask::OnBegin(STOREOPERATIONTYPE tyOperation, STOREOPERATIONINFO *pOpInfo, IOperationCancel *pCancel) { // Hold onto this Assert(m_tyOperation == SOT_INVALID); if (pCancel) { m_pCancel = pCancel; m_pCancel->AddRef(); } m_tyOperation = tyOperation; // Party On return(S_OK); } STDMETHODIMP CNewsTask::OnProgress(STOREOPERATIONTYPE tyOperation, DWORD dwCurrent, DWORD dwMax, LPCSTR pszStatus) { // Close any timeout dialog, if present CallbackCloseTimeout(&m_hTimeout); // NOTE: that you can get more than one type of value for tyOperation. // Most likely, you will get SOT_CONNECTION_STATUS and then the // operation that you might expect. See HotStore.idl and look for // the STOREOPERATION enumeration type for more info. switch (tyOperation) { case SOT_NEW_MAIL_NOTIFICATION: m_dwNewInboxMsgs = dwCurrent; break; } // Done return(S_OK); } STDMETHODIMP CNewsTask::OnTimeout(LPINETSERVER pServer, LPDWORD pdwTimeout, IXPTYPE ixpServerType) { if (!!(m_dwFlags & (DELIVER_NOUI | DELIVER_BACKGROUND))) return(E_FAIL); // Display a timeout dialog return CallbackOnTimeout(pServer, ixpServerType, *pdwTimeout, (ITimeoutCallback *)this, &m_hTimeout); } STDMETHODIMP CNewsTask::CanConnect(LPCSTR pszAccountId, DWORD dwFlags) { HWND hwnd; BOOL fPrompt = TRUE; if (m_pUI) m_pUI->GetWindow(&hwnd); else hwnd = NULL; // Call into general CanConnect Utility if ((m_dwFlags & (DELIVER_NOUI | DELIVER_BACKGROUND)) || (dwFlags & CC_FLAG_DONTPROMPT)) fPrompt = FALSE; return CallbackCanConnect(pszAccountId, hwnd, fPrompt); } STDMETHODIMP CNewsTask::OnLogonPrompt(LPINETSERVER pServer, IXPTYPE ixpServerType) { HWND hwnd; // Close any timeout dialog, if present CallbackCloseTimeout(&m_hTimeout); if (!!(m_dwFlags & (DELIVER_NOUI | DELIVER_BACKGROUND)) && !(ISFLAGSET(pServer->dwFlags, ISF_ALWAYSPROMPTFORPASSWORD) && '\0' == pServer->szPassword[0])) return(S_FALSE); if (m_pUI) m_pUI->GetWindow(&hwnd); else hwnd = NULL; // Call into general OnLogonPrompt Utility return CallbackOnLogonPrompt(hwnd, pServer, ixpServerType); } STDMETHODIMP CNewsTask::OnComplete(STOREOPERATIONTYPE tyOperation, HRESULT hrComplete, LPSTOREOPERATIONINFO pOpInfo, LPSTOREERROR pErrorInfo) { char szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES * 2], szSubject[64]; NEWSTASKSTATE ntsNextState = NTS_MAX; LPSTR pszSubject = NULL; LPSTR pszOpDescription = NULL; BOOL fInsertError = FALSE; // Close any timeout dialog, if present CallbackCloseTimeout(&m_hTimeout); IxpAssert(m_tyOperation != SOT_INVALID); if (m_tyOperation != tyOperation) return(S_OK); switch (tyOperation) { case SOT_PUT_MESSAGE: m_cPartsCompleted ++; // Figure out if we succeeded or failed if (FAILED(hrComplete)) { if (!m_fPartFailed ) { Assert(m_pUI); // Set us up to display the error pszOpDescription = MAKEINTRESOURCE(idsNewsTaskPostError); pszSubject = m_rgMsgInfo[m_cCurPost].pszSubject; if (pszSubject == NULL || *pszSubject == 0) { AthLoadString(idsNoSubject, szSubject, ARRAYSIZE(szSubject)); pszSubject = szSubject; } fInsertError = TRUE; m_cFailed++; m_fPartFailed = TRUE; } } if (m_cPartsCompleted == m_cCurParts) { if (m_fPartFailed) { // Update the progress bar Assert(m_pUI); m_pUI->IncrementProgress(1); // Move on to the next post ntsNextState = NTS_POST_NEXT; } else { ntsNextState = NTS_POST_DISPOSE; } } break; // case SOT_PUT_MESSAGE case SOT_UPDATE_FOLDER: if (FAILED(hrComplete)) { FOLDERINFO fiFolderInfo; Assert(m_pUI); LoadString(g_hLocRes, idsUnreadCountPollErrorFmt, szRes, sizeof(szRes)); if (SUCCEEDED(g_pStore->GetFolderInfo(m_rgidGroups[m_cCurGroup], &fiFolderInfo))) { wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, fiFolderInfo.pszName); g_pStore->FreeRecord(&fiFolderInfo); } else { LoadString(g_hLocRes, idsUnknown, szSubject, sizeof(szSubject)); wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, szSubject); } pszOpDescription = szBuf; fInsertError = TRUE; m_cFailed++; } // Update the progress bar m_pUI->IncrementProgress(1); // Move on to the next group ntsNextState = NTS_NEWMSG_NEXTGROUP; break; // case SOT_UPDATE_FOLDER case SOT_SYNCING_STORE: if (( IXP_E_HTTP_NOT_MODIFIED != hrComplete) && (FAILED(hrComplete))) { LoadString(g_hLocRes, idsHttpPollFailed, szBuf, sizeof(szBuf)); pszOpDescription = szBuf; fInsertError = TRUE; m_cFailed++; } // update the progress bar m_pUI->IncrementProgress(1); // we're done ntsNextState = NTS_NEWMSG_END; break; // case SOT_SYNCING_STORE case SOT_COPYMOVE_MESSAGE: // Update the progress bar Assert(m_pUI); m_pUI->IncrementProgress(1); // Move on to the next post ntsNextState = NTS_POST_NEXT; if (FAILED(hrComplete)) { Assert(m_pUI); pszOpDescription = MAKEINTRESOURCE(IDS_SP_E_CANT_MOVETO_SENTITEMS); fInsertError = TRUE; } break; // case SOT_COPYMOVE_MESSAGE case SOT_GET_HTTP_MINPOLLINGINTERVAL: if (SUCCEEDED(hrComplete) && pOpInfo) { ULARGE_INTEGER uhMinPollingInterval64 = {0}; //Convert it to seconds. uhMinPollingInterval64.QuadPart = pOpInfo->dwMinPollingInterval * 60; //FILETIME is intervals of 100 nano seconds. Need to convert to 100 nanoseconds uhMinPollingInterval64.QuadPart *= HUNDRED_NANOSECONDS; m_pAccount->SetProp(AP_HTTPMAIL_MINPOLLINGINTERVAL, (LPBYTE)&uhMinPollingInterval64, sizeof(uhMinPollingInterval64)); m_pAccount->SetPropDw(AP_HTTPMAIL_GOTPOLLINGINTERVAL, TRUE); break; } default: if (IXP_E_HTTP_NOT_MODIFIED == hrComplete) { hrComplete = S_OK; fInsertError = FALSE; } else { if (FAILED(hrComplete)) { Assert(m_pUI); pszOpDescription = MAKEINTRESOURCE(idsGenericError); fInsertError = TRUE; m_cFailed++; } } break; // default case } // switch if (fInsertError && NULL != pErrorInfo) { Assert(pErrorInfo->hrResult == hrComplete); // These two should not be different TaskUtil_InsertTransportError(ISFLAGCLEAR(m_dwFlags, DELIVER_NOUI), m_pUI, m_eidCur, pErrorInfo, pszOpDescription, pszSubject); } // Move on to next state if (IXP_E_USER_CANCEL == hrComplete) { // User cancelled logon prompt, so just abort everything Cancel(); } else if (NTS_MAX != ntsNextState) { m_state = ntsNextState; PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0); } // Release your cancel object SafeRelease(m_pCancel); m_tyOperation = SOT_INVALID; // Done return(S_OK); } STDMETHODIMP CNewsTask::OnPrompt(HRESULT hrError, LPCTSTR pszText, LPCTSTR pszCaption, UINT uType, INT *piUserResponse) { HWND hwnd; // Close any timeout dialog, if present CallbackCloseTimeout(&m_hTimeout); // Raid 55082 - SPOOLER: SPA/SSL auth to NNTP does not display cert warning and fails. #if 0 if (!!(m_dwFlags & (DELIVER_NOUI | DELIVER_BACKGROUND))) return(E_FAIL); #endif if (m_pUI) m_pUI->GetWindow(&hwnd); else hwnd = NULL; // Call into my swanky utility return CallbackOnPrompt(hwnd, hrError, pszText, pszCaption, uType, piUserResponse); } STDMETHODIMP CNewsTask::OnTimeoutResponse(TIMEOUTRESPONSE eResponse) { // Call into general timeout response utility return CallbackOnTimeoutResponse(eResponse, m_pCancel, &m_hTimeout); } STDMETHODIMP CNewsTask::GetParentWindow(DWORD dwReserved, HWND *phwndParent) { HRESULT hr; if (!!(m_dwFlags & (DELIVER_NOUI | DELIVER_BACKGROUND))) return(E_FAIL); if (m_pUI) { hr = m_pUI->GetWindow(phwndParent); } else { *phwndParent = NULL; hr = E_FAIL; } return(hr); }