Windows2003-3790/inetcore/outlookexpress/mailnews/spooler/ontask.cpp
2020-09-30 16:53:55 +02:00

1667 lines
45 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Copyright (C) 1993-1996 Microsoft Corporation. All Rights Reserved.
//
// MODULE: ontask.cpp
//
// PURPOSE: Implements the offline news task.
//
#include "pch.hxx"
#include "resource.h"
#include "ontask.h"
#include "thormsgs.h"
#include "xputil.h"
#include "mimeutil.h"
#include <stdio.h>
#include "strconst.h"
#include <newsstor.h>
#include "ourguid.h"
#include "taskutil.h"
ASSERTDATA
const static char c_szThis[] = "this";
const PFNONSTATEFUNC COfflineTask::m_rgpfnState[ONTS_MAX] =
{
NULL,
NULL,
&COfflineTask::Download_Init,
NULL,
&COfflineTask::Download_AllMsgs,
&COfflineTask::Download_NewMsgs,
&COfflineTask::Download_MarkedMsgs,
&COfflineTask::Download_Done,
};
const PFNARTICLEFUNC COfflineTask::m_rgpfnArticle[ARTICLE_MAX] =
{
&COfflineTask::Article_GetNext,
NULL,
&COfflineTask::Article_Done
};
#define GROUP_DOWNLOAD_FLAGS(flag) (((flag) & FOLDER_DOWNLOADHEADERS) || \
((flag) & FOLDER_DOWNLOADNEW) || \
((flag) & FOLDER_DOWNLOADALL))
#define CMSGIDALLOC 512
//
// FUNCTION: COfflineTask::COfflineTask()
//
// PURPOSE: Initializes the member variables of the object.
//
COfflineTask::COfflineTask()
{
m_cRef = 1;
m_fInited = FALSE;
m_dwFlags = 0;
m_state = ONTS_IDLE;
m_eidCur = 0;
m_pInfo = NULL;
m_szAccount[0] = 0;
m_cEvents = 0;
m_fDownloadErrors = FALSE;
m_fFailed = FALSE;
m_fNewHeaders = FALSE;
m_fCancel = FALSE;
m_pBindCtx = NULL;
m_pUI = NULL;
m_pFolder = NULL;
m_hwnd = 0;
m_dwLast = 0;
m_dwPrev = 0;
m_cDownloaded = 0;
m_dwPrevHigh = 0;
m_dwNewInboxMsgs = 0;
m_pList = NULL;
m_pCancel = NULL;
m_hTimeout = NULL;
m_tyOperation = SOT_INVALID;
}
//
// FUNCTION: COfflineTask::~COfflineTask()
//
// PURPOSE: Frees any resources allocated during the life of the class.
//
COfflineTask::~COfflineTask()
{
DestroyWindow(m_hwnd);
SafeMemFree(m_pInfo);
SafeMemFree(m_pList);
SafeRelease(m_pBindCtx);
SafeRelease(m_pUI);
CallbackCloseTimeout(&m_hTimeout);
SafeRelease(m_pCancel);
if (m_pFolder)
{
m_pFolder->Close();
SideAssert(0 == m_pFolder->Release());
}
}
HRESULT COfflineTask::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;
if (NULL == *ppvObj)
return (E_NOINTERFACE);
AddRef();
return (S_OK);
}
ULONG COfflineTask::AddRef(void)
{
ULONG cRefT;
cRefT = ++m_cRef;
return (cRefT);
}
ULONG COfflineTask::Release(void)
{
ULONG cRefT;
cRefT = --m_cRef;
if (0 == cRefT)
delete this;
return (cRefT);
}
static const char c_szOfflineTask[] = "Offline Task";
//
// FUNCTION: COfflineTask::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:
// <in> dwFlags - Flags to tell us what types of things to do
// <in> pBindCtx - Pointer to the bind context interface we are to use
//
// RETURN VALUE:
// E_INVALIDARG
// SP_E_ALREADYINITIALIZED
// S_OK
// E_OUTOFMEMORY
//
HRESULT COfflineTask::Init(DWORD dwFlags, ISpoolerBindContext *pBindCtx)
{
// Validate the arguments
Assert(pBindCtx != NULL);
// Check to see if we've been initialzed already
Assert(!m_fInited);
// 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_szOfflineTask, &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_szOfflineTask;
wc.hIcon = NULL;
wc.hIconSm = NULL;
RegisterClassEx(&wc);
}
m_hwnd = CreateWindow(c_szOfflineTask, NULL, WS_POPUP, 10, 10, 10, 10,
GetDesktopWindow(), NULL, g_hInst, this);
if (!m_hwnd)
return(E_OUTOFMEMORY);
m_fInited = TRUE;
return(S_OK);
}
//
// FUNCTION: COfflineTask::BuildEvents()
//
// PURPOSE: This method is called by the spooler engine telling us to create
// and event list for the account specified.
//
// PARAMETERS:
// <in> pAccount - Account object to build the event list for
//
// RETURN VALUE:
// SP_E_UNINITALIZED
// E_INVALIDARG
// S_OK
//
HRESULT COfflineTask::BuildEvents(ISpoolerUI *pSpoolerUI, IImnAccount *pAccount, FOLDERID idFolder)
{
HRESULT hr;
// Validate the arguments
Assert(pAccount != NULL);
Assert(pSpoolerUI != NULL);
// Check to see if we've been initalized
Assert(m_fInited);
// 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();
hr = InsertGroups(pAccount, idFolder);
return(hr);
}
//
// FUNCTION: COfflineTask::InsertGroups()
//
// PURPOSE: Scans the specified account for groups that have an update
// property or marked messages.
//
// PARAMETERS:
// <in> szAccount - Name of the account to check
// <in> pAccount - Pointer to the IImnAccount object for szAccount
//
// RETURN VALUE:
// S_OK
// E_OUTOFMEMORY
//
HRESULT COfflineTask::InsertGroups(IImnAccount *pAccount, FOLDERID idFolder)
{
FOLDERINFO info = { 0 };
HRESULT hr = S_OK;
DWORD dwFlags = 0;
DWORD ids;
TCHAR szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES];
EVENTID eid;
ONEVENTINFO *pei = NULL;
BOOL fIMAP = FALSE;
DWORD dwServerFlags;
// Figure out if this is NNTP or IMAP
if (SUCCEEDED(pAccount->GetServerTypes(&dwServerFlags)) && (dwServerFlags & (SRV_IMAP | SRV_HTTPMAIL)))
fIMAP = TRUE;
if (FOLDERID_INVALID != idFolder)
{
// Fill Folder
hr = g_pStore->GetFolderInfo(idFolder, &info);
if (FAILED(hr))
return hr;
// Figure out what we're downloading
ids = 0;
if (m_dwFlags & DELIVER_OFFLINE_HEADERS)
{
dwFlags = FOLDER_DOWNLOADHEADERS;
if (m_dwFlags & DELIVER_OFFLINE_MARKED)
ids = idsDLHeadersAndMarked;
else
ids = idsDLHeaders;
}
else if (m_dwFlags & DELIVER_OFFLINE_NEW)
{
dwFlags = FOLDER_DOWNLOADNEW;
if (m_dwFlags & DELIVER_OFFLINE_MARKED)
ids = idsDLNewMsgsAndMarked;
else
ids = idsDLNewMsgs;
}
else if (m_dwFlags & DELIVER_OFFLINE_ALL)
{
dwFlags = FOLDER_DOWNLOADALL;
ids = idsDLAllMsgs;
}
else if (m_dwFlags & DELIVER_OFFLINE_MARKED)
{
ids = idsDLMarkedMsgs;
}
// Create the event description
Assert(ids);
AthLoadString(ids, szRes, ARRAYSIZE(szRes));
wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, info.pszName);
// Allocate a structure to save as our twinkie
if (!MemAlloc((LPVOID *) &pei, sizeof(ONEVENTINFO)))
{
g_pStore->FreeRecord(&info);
return(E_OUTOFMEMORY);
}
StrCpyN(pei->szGroup, info.pszName, ARRAYSIZE(pei->szGroup));
pei->idGroup = info.idFolder;
pei->dwFlags = dwFlags;
pei->fMarked = m_dwFlags & DELIVER_OFFLINE_MARKED;
pei->fIMAP = fIMAP;
// Insert the event into the spooler
hr = m_pBindCtx->RegisterEvent(szBuf, this, (DWORD_PTR) pei, pAccount, &eid);
if (SUCCEEDED(hr))
m_cEvents++;
g_pStore->FreeRecord(&info);
}
else
{
//Either Sync All or Send & Receive
Assert(m_idAccount != FOLDERID_INVALID);
BOOL fInclude = FALSE;
if (!(m_dwFlags & DELIVER_OFFLINE_SYNC) && !(m_dwFlags & DELIVER_NOSKIP))
{
DWORD dw;
if (dwServerFlags & SRV_IMAP)
{
if (SUCCEEDED(pAccount->GetPropDw(AP_IMAP_POLL, &dw)) && dw)
{
fInclude = TRUE;
}
}
else
{
if (dwServerFlags & SRV_HTTPMAIL)
{
if (SUCCEEDED(pAccount->GetPropDw(AP_HTTPMAIL_POLL, &dw)) && dw)
{
fInclude = TRUE;
}
}
}
}
else
fInclude = TRUE;
if (fInclude)
hr = InsertAllGroups(m_idAccount, pAccount, fIMAP);
}
return (hr);
}
HRESULT COfflineTask::InsertAllGroups(FOLDERID idParent, IImnAccount *pAccount, BOOL fIMAP)
{
FOLDERINFO info = { 0 };
IEnumerateFolders *pEnum = NULL;
HRESULT hr = S_OK;
DWORD dwFlags = 0;
BOOL fMarked;
DWORD ids;
TCHAR szRes[CCHMAX_STRINGRES], szBuf[CCHMAX_STRINGRES];
EVENTID eid;
ONEVENTINFO *pei = NULL;
BOOL fSubscribedOnly = TRUE;
if (fIMAP)
fSubscribedOnly = FALSE;
Assert(idParent != FOLDERID_INVALID);
hr = g_pStore->EnumChildren(idParent, fSubscribedOnly, &pEnum);
if (FAILED(hr))
return(hr);
// Walk the list of groups and add them to the queue as necessary
while (S_OK == pEnum->Next(1, &info, NULL))
{
// If the download flags are set for this group, insert it
dwFlags = info.dwFlags;
HasMarkedMsgs(info.idFolder, &fMarked);
if (GROUP_DOWNLOAD_FLAGS(dwFlags) || fMarked)
{
// Figure out what we're downloading
ids = 0;
if (dwFlags & FOLDER_DOWNLOADHEADERS)
{
if (fMarked)
ids = idsDLHeadersAndMarked;
else
ids = idsDLHeaders;
}
else if (dwFlags & FOLDER_DOWNLOADNEW)
{
if (fMarked)
ids = idsDLNewMsgsAndMarked;
else
ids = idsDLNewMsgs;
}
else if (dwFlags & FOLDER_DOWNLOADALL)
{
ids = idsDLAllMsgs;
}
else if (fMarked)
{
ids = idsDLMarkedMsgs;
}
// Create the event description
Assert(ids);
AthLoadString(ids, szRes, ARRAYSIZE(szRes));
wnsprintf(szBuf, ARRAYSIZE(szBuf), szRes, info.pszName);
// Allocate a structure to save as our twinkie
if (!MemAlloc((LPVOID *) &pei, sizeof(ONEVENTINFO)))
{
g_pStore->FreeRecord(&info);
hr = E_OUTOFMEMORY;
break;
}
StrCpyN(pei->szGroup, info.pszName, ARRAYSIZE(pei->szGroup));
pei->idGroup = info.idFolder;
pei->dwFlags = dwFlags;
pei->fMarked = fMarked;
pei->fIMAP = fIMAP;
// Insert the event into the spooler
hr = m_pBindCtx->RegisterEvent(szBuf, this, (DWORD_PTR) pei, pAccount, &eid);
if (FAILED(hr))
{
g_pStore->FreeRecord(&info);
break;
}
m_cEvents++;
}
// Recurse on any children
if (info.dwFlags & FOLDER_HASCHILDREN)
{
hr = InsertAllGroups(info.idFolder, pAccount, fIMAP);
if (FAILED(hr))
break;
}
g_pStore->FreeRecord(&info);
}
pEnum->Release();
return hr;
}
//
// FUNCTION: COfflineTask::Execute()
//
// PURPOSE: This signals our task to start executing an event.
//
// PARAMETERS:
// <in> pSpoolerUI - Pointer of the UI object we'll display progress through
// <in> eid - ID of the event to execute
// <in> dwTwinkie - Our extra information we associated with the event
//
// RETURN VALUE:
// SP_E_EXECUTING
// S_OK
// E_INVALIDARG
// SP_E_UNINITIALIZED
//
HRESULT COfflineTask::Execute(EVENTID eid, DWORD_PTR dwTwinkie)
{
// Make sure we're already idle
Assert(m_state == ONTS_IDLE)
// Make sure we're initialized
Assert(m_fInited);
Assert(m_pInfo == NULL);
// Copy the event id and event info
m_eidCur = eid;
m_pInfo = (ONEVENTINFO *) dwTwinkie;
// Forget UI stuff if we're just going to cancel everything
if (FALSE == m_fCancel)
{
// Update the event UI to an executing state
Assert(m_pUI);
m_pUI->UpdateEventState(m_eidCur, -1, NULL, MAKEINTRESOURCE(idsStateExecuting));
m_pUI->SetProgressRange(1);
// Set up the progress
SetGeneralProgress((LPSTR)idsInetMailConnectingHost, m_szAccount);
if (m_pInfo->fIMAP)
m_pUI->SetAnimation(idanInbox, TRUE);
else
m_pUI->SetAnimation(idanDownloadNews, TRUE);
}
m_state = ONTS_INIT;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
HRESULT COfflineTask::CancelEvent(EVENTID eid, DWORD_PTR dwTwinkie)
{
// Make sure we're initialized
Assert(m_fInited);
Assert(dwTwinkie != 0);
MemFree((ONEVENTINFO *)dwTwinkie);
return(S_OK);
}
//
// FUNCTION: <???>
//
// PURPOSE: <???>
//
// PARAMETERS:
// <???>
//
// RETURN VALUE:
// <???>
//
// COMMENTS:
// <???>
//
HRESULT COfflineTask::ShowProperties(HWND hwndParent, EVENTID eid, DWORD_PTR dwTwinkie)
{
return (E_NOTIMPL);
}
//
// FUNCTION: <???>
//
// PURPOSE: <???>
//
// PARAMETERS:
// <???>
//
// RETURN VALUE:
// <???>
//
// COMMENTS:
// <???>
//
HRESULT COfflineTask::GetExtendedDetails(EVENTID eid, DWORD_PTR dwTwinkie,
LPSTR *ppszDetails)
{
return (E_NOTIMPL);
}
//
// FUNCTION: <???>
//
// PURPOSE: <???>
//
// PARAMETERS:
// <???>
//
// RETURN VALUE:
// <???>
//
// COMMENTS:
// <???>
//
HRESULT COfflineTask::Cancel(void)
{
Assert(m_state != ONTS_IDLE);
m_fCancel = TRUE;
m_state = ONTS_END;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return (S_OK);
}
//
// FUNCTION: COfflineTask::TaskWndProc()
//
// PURPOSE: Hidden window that processes messages for this task.
//
LRESULT CALLBACK COfflineTask::TaskWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
COfflineTask *pThis = (COfflineTask *) GetProp(hwnd, c_szThis);
switch (uMsg)
{
case WM_CREATE:
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT) lParam;
pThis = (COfflineTask *) pcs->lpCreateParams;
SetProp(hwnd, c_szThis, (LPVOID) pThis);
return (0);
}
case NTM_NEXTSTATE:
if (pThis)
{
pThis->AddRef();
pThis->NextState();
pThis->Release();
}
return (0);
case NTM_NEXTARTICLESTATE:
if (pThis)
{
pThis->AddRef();
if (m_rgpfnArticle[pThis->m_as])
(pThis->*(m_rgpfnArticle[pThis->m_as]))();
pThis->Release();
}
return (0);
case WM_DESTROY:
RemoveProp(hwnd, c_szThis);
break;
}
return (DefWindowProc(hwnd, uMsg, wParam, lParam));
}
//
// FUNCTION: COfflineTask::NextState()
//
// PURPOSE: Executes the function for the current state
//
void COfflineTask::NextState(void)
{
if (m_fCancel)
m_state = ONTS_END;
if (NULL != m_rgpfnState[m_state])
(this->*(m_rgpfnState[m_state]))();
}
//
// FUNCTION: COfflineTask::Download_Init()
//
// PURPOSE: Does the initialization needed to download headers and messages
// for a particular newsgroup.
//
HRESULT COfflineTask::Download_Init(void)
{
HRESULT hr;
SYNCFOLDERFLAGS flags = SYNC_FOLDER_DEFAULT;
FOLDERINFO info;
Assert(m_pFolder == NULL);
Assert(0 == flags); // If this isn't 0, please verify correctness
hr = g_pStore->OpenFolder(m_pInfo->idGroup, NULL, NOFLAGS, &m_pFolder);
if (FAILED(hr))
{
goto Failure;
}
Assert(m_pFolder != NULL);
hr = g_pStore->GetFolderInfo(m_pInfo->idGroup, &info);
if (FAILED(hr))
{
goto Failure;
}
if (m_pInfo->fIMAP)
{
// Get highest Msg ID the brute-force way (IMAP doesn't set dwClientHigh)
GetHighestCachedMsgID(m_pFolder, &m_dwPrevHigh);
}
else
m_dwPrevHigh = info.dwClientHigh;
g_pStore->FreeRecord(&info);
// Update the UI to an executing state
Assert(m_pUI);
m_pUI->UpdateEventState(m_eidCur, -1, NULL, MAKEINTRESOURCE(idsStateExecuting));
m_fDownloadErrors = FALSE;
// Check to see if the user wants us to download new headers
if (GROUP_DOWNLOAD_FLAGS(m_pInfo->dwFlags))
{
if (!(m_pInfo->dwFlags & FOLDER_DOWNLOADALL) || m_pInfo->fIMAP)
flags = SYNC_FOLDER_NEW_HEADERS | SYNC_FOLDER_CACHED_HEADERS;
else
flags = SYNC_FOLDER_ALLFLAGS;
// Update Progress
SetGeneralProgress((LPSTR)idsLogCheckingNewMessages, m_pInfo->szGroup);
}
else
{
m_state = ONTS_ALLMSGS;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
// Before we download any headers, we need to make a note of what the current
// server high is so we know which articles are new.
hr = m_pFolder->Synchronize(flags, 0, (IStoreCallback *)this);
Assert(hr != S_OK);
if (hr == E_PENDING)
hr = S_OK;
if (m_pInfo->fIMAP)
{
m_pUI->SetAnimation(idanInbox, TRUE);
m_pBindCtx->Notify(DELIVERY_NOTIFY_RECEIVING, 0);
}
else
{
m_pUI->SetAnimation(idanDownloadNews, TRUE);
m_pBindCtx->Notify(DELIVERY_NOTIFY_RECEIVING_NEWS, 0);
}
Failure:
if (FAILED(hr))
{
// $$$$BUGBUG$$$$
InsertError((LPSTR)idsLogErrorSwitchGroup, m_pInfo->szGroup, m_szAccount);
m_fFailed = TRUE;
m_state = ONTS_END;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
}
return (hr);
}
//
// FUNCTION: COfflineTask::Download_AllMsgs()
//
// PURPOSE:
//
//
HRESULT COfflineTask::Download_AllMsgs(void)
{
HRESULT hr;
DWORD cMsgs, cMsgsBuf;
LPMESSAGEID pMsgId;
MESSAGEIDLIST list;
MESSAGEINFO MsgInfo = {0};
HROWSET hRowset = NULL;
// Check to see if we even want to download all messages
if (!(m_pInfo->dwFlags & FOLDER_DOWNLOADALL))
{
m_state = ONTS_NEWMSGS;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
// We need to determine a list of messages to download. What we're looking
// to do is download all of the messages that we know about which are unread.
// To do this, we need to find the intersection of the unread range list and
// the known range list.
// Create a Rowset
hr = m_pFolder->CreateRowset(IINDEX_PRIMARY, 0, &hRowset);
if (FAILED(hr))
{
goto Failure;
}
cMsgs = 0;
cMsgsBuf = 0;
pMsgId = NULL;
// Get the first message
while (S_OK == m_pFolder->QueryRowset(hRowset, 1, (void **)&MsgInfo, NULL))
{
if (0 == (MsgInfo.dwFlags & ARF_HASBODY) && 0 == (MsgInfo.dwFlags & ARF_IGNORE))
{
if (cMsgs == cMsgsBuf)
{
if (!MemRealloc((void **)&pMsgId, (cMsgsBuf + CMSGIDALLOC) * sizeof(MESSAGEID)))
{
m_pFolder->FreeRecord(&MsgInfo);
hr = E_OUTOFMEMORY;
break;
}
cMsgsBuf += CMSGIDALLOC;
}
pMsgId[cMsgs] = MsgInfo.idMessage;
cMsgs++;
}
// Free the header info
m_pFolder->FreeRecord(&MsgInfo);
}
// Release Lock
m_pFolder->CloseRowset(&hRowset);
// TODO: error handling
Assert(!FAILED(hr));
// Check to see if we found anything
if (cMsgs == 0)
{
// Nothing to download. We should move on to the marked download
// state.
Assert(pMsgId == NULL);
m_state = ONTS_MARKEDMSGS;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
// Update the general progress
SetGeneralProgress((LPSTR)idsLogStartDownloadAll, m_pInfo->szGroup);
list.cAllocated = 0;
list.cMsgs = cMsgs;
list.prgidMsg = pMsgId;
// Ask for the first article
hr = Article_Init(&list);
if (pMsgId != NULL)
MemFree(pMsgId);
Failure:
if (FAILED(hr))
{
// $$$$BUGBUG$$$$
InsertError((LPSTR)idsLogErrorSwitchGroup, m_pInfo->szGroup, m_szAccount);
m_fFailed = TRUE;
m_state = ONTS_END;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
}
return (hr);
}
//
// FUNCTION: COfflineTask::Download_NewMsgs()
//
// PURPOSE: This function determines if there are any new messages to be
// downloaded. If so, it creates a list of message numbers that
// need to be downloaded.
//
HRESULT COfflineTask::Download_NewMsgs(void)
{
HRESULT hr;
ROWORDINAL iRow = 0;
BOOL fFound;
HROWSET hRowset;
DWORD cMsgs, cMsgsBuf;
LPMESSAGEID pMsgId;
MESSAGEIDLIST list;
MESSAGEINFO Message = {0};
// Check to see if there are even new messages to download
// Check to see if we even want to download all messages
if (!(m_pInfo->dwFlags & FOLDER_DOWNLOADNEW) || !m_fNewHeaders)
{
// Move the next state
m_state = ONTS_MARKEDMSGS;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
// We've got new messages, build a range list of those message numbers.
// This range list is essentially every number in the known range above
// m_dwPrevHigh.
hr = S_OK;
cMsgs = 0;
cMsgsBuf = 0;
pMsgId = NULL;
fFound = FALSE;
// TODO: this method of figuring out if there are new msgs isn't going to work all
// the time. if the previous high is removed from the store during syncing (cancelled
// news post, deleted msg, expired news post, etc) and new headers are downloaded,
// we won't pull down the new msgs. we need a better way of detecting new hdrs and
// pulling down there bodies
if (m_dwPrevHigh > 0)
{
Message.idMessage = (MESSAGEID)m_dwPrevHigh;
// Find This Record. If this fails, we go ahead and do a full scan which is less
// efficient, but OK.
if (DB_S_FOUND == m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &Message, &iRow))
{
m_pFolder->FreeRecord(&Message);
}
}
hr = m_pFolder->CreateRowset(IINDEX_PRIMARY, 0, &hRowset);
if (SUCCEEDED(hr))
{
if (SUCCEEDED(m_pFolder->SeekRowset(hRowset, SEEK_ROWSET_BEGIN, iRow, NULL)))
{
// Get the first message
while (S_OK == m_pFolder->QueryRowset(hRowset, 1, (void **)&Message, NULL))
{
if (cMsgs == cMsgsBuf)
{
if (!MemRealloc((void **)&pMsgId, (cMsgsBuf + CMSGIDALLOC) * sizeof(MESSAGEID)))
{
m_pFolder->FreeRecord(&Message);
hr = E_OUTOFMEMORY;
break;
}
cMsgsBuf += CMSGIDALLOC;
}
// It's possible to have already downloaded the body if the message was
// watched. It's also possible for the message to be part of an ignored
// thread.
if (0 == (Message.dwFlags & ARF_HASBODY) && 0 == (Message.dwFlags & ARF_IGNORE) && (Message.idMessage >= (MESSAGEID) m_dwPrevHigh))
{
pMsgId[cMsgs] = Message.idMessage;
cMsgs++;
}
// Free the header info
m_pFolder->FreeRecord(&Message);
}
}
// Release Lock
m_pFolder->CloseRowset(&hRowset);
}
// TODO: error handling
Assert(!FAILED(hr));
// Check to see if there was anything added
if (cMsgs == 0)
{
// Nothing to download. We should move on to the marked download
// state.
m_state = ONTS_MARKEDMSGS;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
// Update the general progress
SetGeneralProgress((LPSTR)idsLogStartDownloadAll, m_pInfo->szGroup);
list.cAllocated = 0;
list.cMsgs = cMsgs;
list.prgidMsg = pMsgId;
// Ask for the first article
hr = Article_Init(&list);
if (pMsgId != NULL)
MemFree(pMsgId);
return(hr);
}
//
// FUNCTION: COfflineTask::Download_MarkedMsgs()
//
// PURPOSE:
//
//
HRESULT COfflineTask::Download_MarkedMsgs(void)
{
HRESULT hr;
HROWSET hRowset;
DWORD cMsgs, cMsgsBuf;
LPMESSAGEID pMsgId;
MESSAGEIDLIST list;
MESSAGEINFO MsgInfo;
// Check to see if we even want to download marked messages
if (!m_pInfo->fMarked)
{
// Move on to the next state
m_state = ONTS_END;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
// We need to determine a list of messages to download. What we're looking
// to do is download all of the messages that are marked which are unread.
// To do this, we need to find the intersection of the unread range list and
// the marked range list.
// Create a Rowset
hr = m_pFolder->CreateRowset(IINDEX_PRIMARY, 0, &hRowset);
if (FAILED(hr))
{
goto Failure;
}
cMsgs = 0;
cMsgsBuf = 0;
pMsgId = NULL;
// Get the first message
while (S_OK == m_pFolder->QueryRowset(hRowset, 1, (void **)&MsgInfo, NULL))
{
if (((MsgInfo.dwFlags & ARF_DOWNLOAD) || (MsgInfo.dwFlags & ARF_WATCH)) && 0 == (MsgInfo.dwFlags & ARF_HASBODY))
{
if (cMsgs == cMsgsBuf)
{
if (!MemRealloc((void **)&pMsgId, (cMsgsBuf + CMSGIDALLOC) * sizeof(MESSAGEID)))
{
m_pFolder->FreeRecord(&MsgInfo);
hr = E_OUTOFMEMORY;
break;
}
cMsgsBuf += CMSGIDALLOC;
}
pMsgId[cMsgs] = MsgInfo.idMessage;
cMsgs++;
}
// Free the header info
m_pFolder->FreeRecord(&MsgInfo);
}
// Release Lock
m_pFolder->CloseRowset(&hRowset);
// TODO: error handling
Assert(!FAILED(hr));
// Check to see if we found anything
if (cMsgs == 0)
{
// Nothing to download. We should move on to next state.
m_state = ONTS_END;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
// Update the general progress
SetGeneralProgress((LPSTR)idsLogStartDownloadAll, m_pInfo->szGroup);
list.cAllocated = 0;
list.cMsgs = cMsgs;
list.prgidMsg = pMsgId;
// Ask for the first article
hr = Article_Init(&list);
if (pMsgId != NULL)
MemFree(pMsgId);
Failure:
if (FAILED(hr))
{
// $$$$BUGBUG$$$$
InsertError((LPSTR)idsLogErrorSwitchGroup, m_pInfo->szGroup, m_szAccount);
m_fFailed = TRUE;
m_state = ONTS_END;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
}
return (hr);
}
//
// FUNCTION: COfflineTask::Download_Done()
//
// PURPOSE:
//
//
HRESULT COfflineTask::Download_Done(void)
{
// Make sure we don't get freed before we can clean up
AddRef();
// 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);
else if (m_fFailed)
m_pBindCtx->EventDone(m_eidCur, EVENT_FAILED);
else if (m_fDownloadErrors)
m_pBindCtx->EventDone(m_eidCur, EVENT_WARNINGS);
else
m_pBindCtx->EventDone(m_eidCur, EVENT_SUCCEEDED);
m_cEvents--;
if (m_pFolder != NULL)
{
m_pFolder->Close();
m_pFolder->Release();
m_pFolder = NULL;
}
m_state = ONTS_IDLE;
SafeMemFree(m_pInfo);
Release();
return (S_OK);
}
//
// FUNCTION: COfflineTask::InsertError()
//
// PURPOSE: This function is a wrapper for the ISpoolerUI::InsertError()
// that takes the responsibility of loading the string resource
// and constructing the error message.
//
void COfflineTask::InsertError(const TCHAR *pFmt, ...)
{
int i;
va_list pArgs;
LPCTSTR pszT;
TCHAR szFmt[CCHMAX_STRINGRES];
DWORD cbWritten;
TCHAR szBuf[2 * CCHMAX_STRINGRES];
// If we were passed a string resource ID, then we need to load it
if (IS_INTRESOURCE(pFmt))
{
AthLoadString(PtrToUlong(pFmt), szFmt, ARRAYSIZE(szFmt));
pszT = szFmt;
}
else
pszT = pFmt;
// Format the string
va_start(pArgs, pFmt);
i = wvnsprintf(szBuf, ARRAYSIZE(szBuf), pszT, pArgs);
va_end(pArgs);
// Send the string to the UI
m_pUI->InsertError(m_eidCur, szBuf);
}
//
// FUNCTION: COfflineTask::SetSpecificProgress()
//
// PURPOSE: This function is a wrapper for the ISpoolerUI::SetSpecificProgress()
// that takes the responsibility of loading the string resource
// and constructing the error message.
//
void COfflineTask::SetSpecificProgress(const TCHAR *pFmt, ...)
{
int i;
va_list pArgs;
LPCTSTR pszT;
TCHAR szFmt[CCHMAX_STRINGRES];
DWORD cbWritten;
TCHAR szBuf[2 * CCHMAX_STRINGRES];
// If we were passed a string resource ID, then we need to load it
if (IS_INTRESOURCE(pFmt))
{
AthLoadString(PtrToUlong(pFmt), szFmt, ARRAYSIZE(szFmt));
pszT = szFmt;
}
else
pszT = pFmt;
// Format the string
va_start(pArgs, pFmt);
i = wvnsprintf(szBuf, ARRAYSIZE(szBuf), pszT, pArgs);
va_end(pArgs);
// Send the string to the UI
m_pUI->SetSpecificProgress(szBuf);
}
//
// FUNCTION: COfflineTask::SetGeneralProgress()
//
// PURPOSE: This function is a wrapper for the ISpoolerUI::SetGeneralProgress()
// that takes the responsibility of loading the string resource
// and constructing the error message.
//
void COfflineTask::SetGeneralProgress(const TCHAR *pFmt, ...)
{
int i;
va_list pArgs;
LPCTSTR pszT;
TCHAR szFmt[CCHMAX_STRINGRES];
DWORD cbWritten;
TCHAR szBuf[2 * CCHMAX_STRINGRES];
// If we were passed a string resource ID, then we need to load it
if (IS_INTRESOURCE(pFmt))
{
AthLoadString(PtrToUlong(pFmt), szFmt, ARRAYSIZE(szFmt));
pszT = szFmt;
}
else
pszT = pFmt;
// Format the string
va_start(pArgs, pFmt);
i = wvnsprintf(szBuf, ARRAYSIZE(szBuf), pszT, pArgs);
va_end(pArgs);
// Send the string to the UI
m_pUI->SetGeneralProgress(szBuf);
}
//
// FUNCTION: COfflineTask::Article_Init()
//
// PURPOSE: Initializes the article download substate machine.
//
// PARAMETERS:
// <in> pRange - Range list of articles to download.
//
HRESULT COfflineTask::Article_Init(MESSAGEIDLIST *pList)
{
HRESULT hr;
Assert(pList != NULL);
Assert(pList->cMsgs > 0);
Assert(m_pList == NULL);
hr = CloneMessageIDList(pList, &m_pList);
if (FAILED(hr))
return(hr);
// Determine the first and the size
m_cDownloaded = 0;
m_cCur = 0;
m_dwNewInboxMsgs = 0;
// Set up the UI
SetSpecificProgress((LPSTR)idsIMAPDnldProgressFmt, 0, m_pList->cMsgs);
m_pUI->SetProgressRange((WORD)m_pList->cMsgs);
// Request the first one
m_as = ARTICLE_GETNEXT;
PostMessage(m_hwnd, NTM_NEXTARTICLESTATE, 0, 0);
return(S_OK);
}
//
// FUNCTION: COfflineTask::Article_GetNext()
//
// PURPOSE: Determines the next article in the range of articles to
// download and requests that article from the server.
//
HRESULT COfflineTask::Article_GetNext(void)
{
HRESULT hr;
LPMIMEMESSAGE pMsg = NULL;
if (NULL == m_pFolder)
return(S_OK);
// Find out the next article number
if (m_cCur == m_pList->cMsgs)
{
// We're done. Exit.
m_as = ARTICLE_END;
PostMessage(m_hwnd, NTM_NEXTARTICLESTATE, 0, 0);
return(S_OK);
}
m_cDownloaded++;
// (YST) Bug 97397 We should send notification message from here too, because this is
// only one availble place for HTTP (fIMAP is set for HTTP).
if(m_pInfo->fIMAP)
OnProgress(SOT_NEW_MAIL_NOTIFICATION, 1, 0, NULL);
// Update the progress UI
SetSpecificProgress((LPSTR)idsIMAPDnldProgressFmt, m_cDownloaded, m_pList->cMsgs);
m_pUI->IncrementProgress(1);
// Ask for the article
hr = m_pFolder->OpenMessage(m_pList->prgidMsg[m_cCur], 0, &pMsg, (IStoreCallback *)this);
if (pMsg != NULL)
pMsg->Release();
m_cCur++;
if (hr == E_PENDING)
{
m_as = ARTICLE_ONRESP;
}
else
{
// Whatever happened, we should move on to the next article.
m_as = ARTICLE_GETNEXT;
PostMessage(m_hwnd, NTM_NEXTARTICLESTATE, 0, 0);
}
return(S_OK);
}
//
// FUNCTION: COfflineTask::Article_Done()
//
// PURPOSE: When we've downloaded the last article, this function cleans
// up and moves us to the next state.
//
HRESULT COfflineTask::Article_Done(void)
{
// Free the range list we were working off of
MemFree(m_pList);
m_pList = NULL;
// Move to the next state. The next state is either get marked or done.
if (m_state == ONTS_MARKEDMSGS)
m_state = ONTS_END;
else
m_state = ONTS_MARKEDMSGS;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
return(S_OK);
}
STDMETHODIMP COfflineTask::IsDialogMessage(LPMSG pMsg)
{
return S_FALSE;
}
STDMETHODIMP COfflineTask::OnFlagsChanged(DWORD dwFlags)
{
m_dwFlags = dwFlags;
return (S_OK);
}
STDMETHODIMP COfflineTask::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;
m_dwPrev = 0;
m_dwLast = 0;
// Party On
return(S_OK);
}
STDMETHODIMP COfflineTask::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_CONNECTION_STATUS:
break;
case SOT_NEW_MAIL_NOTIFICATION:
m_dwNewInboxMsgs += dwCurrent;
break;
default:
if (m_state == ONTS_INIT)
{
// Update UI
if (dwMax > m_dwLast)
{
m_dwLast = dwMax;
m_pUI->SetProgressRange((WORD)m_dwLast);
}
SetSpecificProgress((LPSTR)idsDownloadingHeaders, dwCurrent, m_dwLast);
m_pUI->IncrementProgress((WORD) (dwCurrent - m_dwPrev));
m_dwPrev = dwCurrent;
}
} // switch
// Done
return(S_OK);
}
STDMETHODIMP COfflineTask::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 COfflineTask::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 COfflineTask::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 COfflineTask::OnComplete(STOREOPERATIONTYPE tyOperation, HRESULT hrComplete,
LPSTOREOPERATIONINFO pOpInfo, LPSTOREERROR pErrorInfo)
{
HRESULT hr;
DWORD dw;
BOOL fUserCancel = FALSE;
// Close any timeout dialog, if present
CallbackCloseTimeout(&m_hTimeout);
Assert(m_tyOperation != SOT_INVALID);
if (m_tyOperation != tyOperation)
return(S_OK);
switch (hrComplete)
{
case STORE_E_EXPIRED:
case IXP_E_HTTP_NOT_MODIFIED:
// Completely ignore errors due to expired/deleted messages
hrComplete = S_OK;
break;
case STORE_E_OPERATION_CANCELED:
case HR_E_USER_CANCEL_CONNECT:
case IXP_E_USER_CANCEL:
fUserCancel = TRUE;
break;
}
if (FAILED(hrComplete))
{
LPSTR pszOpDescription = NULL;
LPSTR pszSubject = NULL;
MESSAGEINFO Message;
BOOL fFreeMsgInfo = FALSE;
char szBuf[CCHMAX_STRINGRES], szFmt[CCHMAX_STRINGRES];
switch (tyOperation)
{
case SOT_GET_MESSAGE:
// we've already incremented m_cCur by the time we get this
Assert((m_cCur - 1) < m_pList->cMsgs);
Message.idMessage = m_pList->prgidMsg[m_cCur - 1];
pszOpDescription = MAKEINTRESOURCE(idsNewsTaskArticleError);
if (DB_S_FOUND == m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &Message, NULL))
{
fFreeMsgInfo = TRUE;
pszSubject = Message.pszSubject;
}
break; // case SOT_GET_MESSAGE
case SOT_SYNC_FOLDER:
LoadString(g_hLocRes, idsHeaderDownloadFailureFmt, szFmt, sizeof(szFmt));
wnsprintf(szBuf, ARRAYSIZE(szBuf), szFmt, (NULL == m_pInfo) ? c_szEmpty : m_pInfo->szGroup);
pszOpDescription = szBuf;
break;
default:
LoadString(g_hLocRes, idsMessageSyncFailureFmt, szFmt, sizeof(szFmt));
wnsprintf(szBuf, ARRAYSIZE(szBuf), szFmt, (NULL == m_pInfo) ? c_szEmpty : m_pInfo->szGroup);
pszOpDescription = szBuf;
break; // default case
} // switch
m_fDownloadErrors = TRUE;
if (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);
}
if (fFreeMsgInfo)
m_pFolder->FreeRecord(&Message);
}
if (fUserCancel)
{
// User has cancelled the OnLogonPrompt dialog, so abort EVERYTHING
Cancel();
}
else if (m_state == ONTS_INIT)
{
SetSpecificProgress((LPSTR)idsDownloadingHeaders, m_dwLast, m_dwLast);
m_pUI->IncrementProgress((WORD) (m_dwLast - m_dwPrev));
// Set a flag if we actually downloaded new headers
m_fNewHeaders = (m_dwLast > 0);
// Move to the next state
m_state = ONTS_ALLMSGS;
PostMessage(m_hwnd, NTM_NEXTSTATE, 0, 0);
}
else
{
m_as = ARTICLE_GETNEXT;
PostMessage(m_hwnd, NTM_NEXTARTICLESTATE, 0, 0);
}
// Release your cancel object
SafeRelease(m_pCancel);
m_tyOperation = SOT_INVALID;
// Done
return(S_OK);
}
STDMETHODIMP COfflineTask::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 COfflineTask::OnTimeoutResponse(TIMEOUTRESPONSE eResponse)
{
// Call into general timeout response utility
return CallbackOnTimeoutResponse(eResponse, m_pCancel, &m_hTimeout);
}
STDMETHODIMP COfflineTask::GetParentWindow(DWORD dwReserved, HWND *phwndParent)
{
if (!!(m_dwFlags & (DELIVER_NOUI | DELIVER_BACKGROUND)))
return(E_FAIL);
if (m_pUI)
{
return m_pUI->GetWindow(phwndParent);
}
else
{
*phwndParent = NULL;
return E_FAIL;
}
}