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.
 
 
 
 
 
 

3335 lines
91 KiB

/*
* n e w s s t o r . c p p
*
* Purpose:
* Derives from IMessageServer to implement news specific store communication
*
* Owner:
* cevans.
*
* History:
* May '98: Created
* June '98 Rewrote
*
* Copyright (C) Microsoft Corp. 1998.
*/
#include "pch.hxx"
#include "newsstor.h"
#include "xpcomm.h"
#include "xputil.h"
#include "conman.h"
#include "IMsgSite.h"
#include "note.h"
#include "storutil.h"
#include "storfldr.h"
#include "oerules.h"
#include "ruleutil.h"
#include <rulesmgr.h>
#include <serverq.h>
#include "newsutil.h"
#include "range.h"
#define AssertSingleThreaded AssertSz(m_dwThreadId == GetCurrentThreadId(), "Multi-threading make me sad.")
#define WM_NNTP_BEGIN_OP (WM_USER + 69)
static const char s_szNewsStoreWndClass[] = "Outlook Express NewsStore";
// Get XX Header consts
const BYTE MAXOPS = 3; // maxnumber of HEADER commands to issue
const BYTE DLOVERKILL = 10; // percent to grab more than user's desired chunk [10,..)
const BYTE FRACNEEDED = 8; // percent needed to satisfy user's amount [1,10]
void AddRequestedRange(FOLDERINFO *pInfo, DWORD dwLow, DWORD dwHigh, BOOL *pfReq, BOOL *pfRead);
// SOT_SYNC_FOLDER
static const PFNOPFUNC c_rgpfnSyncFolder[] =
{
&CNewsStore::Connect,
&CNewsStore::Group,
&CNewsStore::ExpireHeaders,
&CNewsStore::Headers
};
// SOT_GET_MESSAGE
static const PFNOPFUNC c_rgpfnGetMessage[] =
{
&CNewsStore::Connect,
&CNewsStore::GroupIfNecessary, // only issue group command if necessary
&CNewsStore::Article
};
// SOT_PUT_MESSAGE
static const PFNOPFUNC c_rgpfnPutMessage[] =
{
&CNewsStore::Connect,
&CNewsStore::Post
};
// SOT_SYNCING_STORE
static const PFNOPFUNC c_rgpfnSyncStore[] =
{
&CNewsStore::Connect,
&CNewsStore::List,
&CNewsStore::DeleteDeadGroups,
&CNewsStore::Descriptions
};
// SOT_GET_NEW_GROUPS
static const PFNOPFUNC c_rgpfnGetNewGroups[] =
{
&CNewsStore::Connect,
&CNewsStore::NewGroups
};
// SOT_UPDATE_FOLDER
static const PFNOPFUNC c_rgpfnUpdateFolder[] =
{
&CNewsStore::Connect,
&CNewsStore::Group
};
// SOT_GET_WATCH_INFO
static const PFNOPFUNC c_rgpfnGetWatchInfo[] =
{
&CNewsStore::Connect,
&CNewsStore::Group,
&CNewsStore::XHdrReferences,
&CNewsStore::XHdrSubject,
&CNewsStore::WatchedArticles
};
//
// FUNCTION: CreateNewsStore()
//
// PURPOSE: Creates the CNewsStore object and returns it's IUnknown
// pointer.
//
// PARAMETERS:
// [in] pUnkOuter - Pointer to the IUnknown that this object should
// aggregate with.
// [out] ppUnknown - Returns the pointer to the newly created object.
//
HRESULT CreateNewsStore(IUnknown *pUnkOuter, IUnknown **ppUnknown)
{
HRESULT hr;
IMessageServer *pServer;
// Trace
TraceCall("CreateNewsStore");
// Invalid Args
Assert(ppUnknown);
// Initialize
*ppUnknown = NULL;
// Create me
CNewsStore *pNew = new CNewsStore();
if (NULL == pNew)
return TraceResult(E_OUTOFMEMORY);
hr = CreateServerQueue((IMessageServer *)pNew, &pServer);
pNew->Release();
if (FAILED(hr))
return(hr);
// Cast to unknown
*ppUnknown = SAFECAST(pServer, IMessageServer *);
// Done
return S_OK;
}
//----------------------------------------------------------------------
// CNewsStore
//----------------------------------------------------------------------
//
//
// FUNCTION: CNewsStore::CNewsStore()
//
// PURPOSE: Constructor
//
CNewsStore::CNewsStore()
{
m_cRef = 1;
m_hwnd = NULL;
m_pStore = NULL;
m_pFolder = NULL;
m_idFolder = FOLDERID_INVALID;
m_idParent = FOLDERID_INVALID;
m_szGroup[0] = 0;
m_szAccountId[0] = 0;
ZeroMemory(&m_op, sizeof(m_op));
m_pROP = NULL;
m_pTransport = NULL;
m_ixpStatus = IXP_DISCONNECTED;
m_dwLastStatusTicks = 0;
ZeroMemory(&m_rInetServerInfo, sizeof(INETSERVER));
m_dwWatchLow = 0;
m_dwWatchHigh = 0;
m_rgpszWatchInfo = 0;
m_fXhdrSubject = 0;
m_cRange.Clear();
m_pTable = NULL;
#ifdef DEBUG
m_dwThreadId = GetCurrentThreadId();
#endif // DEBUG
}
//
//
// FUNCTION: CNewsStore::~CNewsStore()
//
// PURPOSE: Destructor
//
CNewsStore::~CNewsStore()
{
AssertSingleThreaded;
if (m_hwnd != NULL)
DestroyWindow(m_hwnd);
if (m_pTransport)
{
// If we're still connected, drop the connection and then release
if (_FConnected())
m_pTransport->DropConnection();
SideAssert(m_pTransport->Release() == 0);
m_pTransport = NULL;
}
_FreeOperation();
if (m_pROP != NULL)
MemFree(m_pROP);
SafeRelease(m_pStore);
SafeRelease(m_pFolder);
SafeRelease(m_pTable);
}
//
// FUNCTION: CNewsStore::QueryInterface()
//
STDMETHODIMP CNewsStore::QueryInterface(REFIID riid, LPVOID *ppv)
{
// Locals
HRESULT hr=S_OK;
// Stack
TraceCall("CNewsStore::QueryInterface");
AssertSingleThreaded;
// Find IID
if (IID_IUnknown == riid)
*ppv = (IMessageServer *)this;
else if (IID_IMessageServer == riid)
*ppv = (IMessageServer *)this;
else if (IID_ITransportCallback == riid)
*ppv = (ITransportCallback *)this;
else if (IID_ITransportCallbackService == riid)
*ppv = (ITransportCallbackService *)this;
else if (IID_INNTPCallback == riid)
*ppv = (INNTPCallback *)this;
else if (IID_INewsStore == riid)
*ppv = (INewsStore *)this;
else
{
*ppv = NULL;
hr = E_NOINTERFACE;
goto exit;
}
// AddRef It
((IUnknown *)*ppv)->AddRef();
exit:
// Done
return hr;
}
//
// FUNCTION: CNewsStore::AddRef()
//
STDMETHODIMP_(ULONG) CNewsStore::AddRef(void)
{
TraceCall("CNewsStore::AddRef");
AssertSingleThreaded;
return InterlockedIncrement(&m_cRef);
}
//
// FUNCTION: CNewsStore::Release()
//
STDMETHODIMP_(ULONG) CNewsStore::Release(void)
{
TraceCall("CNewsStore::Release");
AssertSingleThreaded;
LONG cRef = InterlockedDecrement(&m_cRef);
Assert(cRef >= 0);
if (0 == cRef)
delete this;
return (ULONG)cRef;
}
HRESULT CNewsStore::Initialize(IMessageStore *pStore, FOLDERID idStoreRoot, IMessageFolder *pFolder, FOLDERID idFolder)
{
HRESULT hr;
FOLDERINFO info;
AssertSingleThreaded;
if (pStore == NULL || idStoreRoot == FOLDERID_INVALID)
return(E_INVALIDARG);
if (!_CreateWnd())
return(E_FAIL);
m_idParent = idStoreRoot;
m_idFolder = idFolder;
ReplaceInterface(m_pStore, pStore);
ReplaceInterface(m_pFolder, pFolder);
hr = m_pStore->GetFolderInfo(idStoreRoot, &info);
if (FAILED(hr))
return(hr);
Assert(!!(info.dwFlags & FOLDER_SERVER));
StrCpyN(m_szAccountId, info.pszAccountId, ARRAYSIZE(m_szAccountId));
m_pStore->FreeRecord(&info);
return(S_OK);
}
HRESULT CNewsStore::ResetFolder(IMessageFolder *pFolder, FOLDERID idFolder)
{
AssertSingleThreaded;
if (pFolder == NULL || idFolder == FOLDERID_INVALID)
return(E_INVALIDARG);
m_idFolder = idFolder;
ReplaceInterface(m_pFolder, pFolder);
return(S_OK);
}
HRESULT CNewsStore::Initialize(FOLDERID idStoreRoot, LPCSTR pszAccountId)
{
AssertSingleThreaded;
if (idStoreRoot == FOLDERID_INVALID || pszAccountId == NULL)
return(E_INVALIDARG);
if (!_CreateWnd())
return(E_FAIL);
m_idParent = idStoreRoot;
m_idFolder = FOLDERID_INVALID;
#pragma prefast(suppress:282, "this macro uses the assignment as part of a test for NULL")
ReplaceInterface(m_pStore, NULL);
#pragma prefast(suppress:282, "this macro uses the assignment as part of a test for NULL")
ReplaceInterface(m_pFolder, NULL);
StrCpyN(m_szAccountId, pszAccountId, ARRAYSIZE(m_szAccountId));
return(S_OK);
}
BOOL CNewsStore::_CreateWnd()
{
WNDCLASS wc;
Assert(m_hwnd == NULL);
if (!GetClassInfo(g_hInst, s_szNewsStoreWndClass, &wc))
{
wc.style = 0;
wc.lpfnWndProc = CNewsStore::NewsStoreWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = g_hInst;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = s_szNewsStoreWndClass;
if (RegisterClass(&wc) == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
return E_FAIL;
}
m_hwnd = CreateWindowEx(WS_EX_TOPMOST,
s_szNewsStoreWndClass,
s_szNewsStoreWndClass,
WS_POPUP,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
g_hInst,
(LPVOID)this);
return (NULL != m_hwnd);
}
// --------------------------------------------------------------------------------
// CHTTPMailServer::_WndProc
// --------------------------------------------------------------------------------
LRESULT CALLBACK CNewsStore::NewsStoreWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
CNewsStore *pThis = (CNewsStore *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (msg)
{
case WM_NCCREATE:
Assert(pThis == NULL);
pThis = (CNewsStore *)((LPCREATESTRUCT)lParam)->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LPARAM)pThis);
break;
case WM_NNTP_BEGIN_OP:
Assert(pThis != NULL);
pThis->_DoOperation();
break;
}
return(DefWindowProc(hwnd, msg, wParam, lParam));
}
HRESULT CNewsStore::_BeginDeferredOperation(void)
{
return (PostMessage(m_hwnd, WM_NNTP_BEGIN_OP, 0, 0) ? E_PENDING : E_FAIL);
}
HRESULT CNewsStore::Close(DWORD dwFlags)
{
AssertSingleThreaded;
// let go of the transport, so that it let's go of us
if (m_op.tyOperation != SOT_INVALID)
m_op.fCancel = TRUE;
if (dwFlags & MSGSVRF_DROP_CONNECTION || dwFlags & MSGSVRF_HANDS_OFF_SERVER)
{
if (_FConnected())
m_pTransport->DropConnection();
}
if (dwFlags & MSGSVRF_HANDS_OFF_SERVER)
{
if (m_pTransport)
{
m_pTransport->HandsOffCallback();
m_pTransport->Release();
m_pTransport = NULL;
}
}
return(S_OK);
}
void CNewsStore::_FreeOperation()
{
FILEADDRESS faStream;
if (m_op.pCallback != NULL)
m_op.pCallback->Release();
if (m_pFolder != NULL && m_op.faStream != 0)
m_pFolder->DeleteStream(m_op.faStream);
if (m_op.pStream != NULL)
m_op.pStream->Release();
if (m_op.pPrevFolders != NULL)
MemFree(m_op.pPrevFolders);
if (m_op.pszGroup != NULL)
MemFree(m_op.pszGroup);
if (m_op.pszArticleId != NULL)
MemFree(m_op.pszArticleId);
ZeroMemory(&m_op, sizeof(OPERATION));
m_op.tyOperation = SOT_INVALID;
}
HRESULT CNewsStore::Connect()
{
INETSERVER rInetServerInfo;
HRESULT hr;
BOOL fInetInit;
IImnAccount *pAccount = NULL;
char szAccountName[CCHMAX_ACCOUNT_NAME];
char szLogFile[MAX_PATH];
DWORD dwLog;
AssertSingleThreaded;
Assert(m_op.pCallback != NULL);
//Bug# 68339
if (g_pAcctMan)
{
hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_szAccountId, &pAccount);
if (FAILED(hr))
return(hr);
fInetInit = FALSE;
if (_FConnected())
{
Assert(m_pTransport != NULL);
hr = m_pTransport->InetServerFromAccount(pAccount, &rInetServerInfo);
if (FAILED(hr))
goto exit;
Assert(m_rInetServerInfo.szServerName[0] != 0);
if (m_rInetServerInfo.rasconntype == rInetServerInfo.rasconntype &&
m_rInetServerInfo.dwPort == rInetServerInfo.dwPort &&
m_rInetServerInfo.fSSL == rInetServerInfo.fSSL &&
m_rInetServerInfo.fTrySicily == rInetServerInfo.fTrySicily &&
m_rInetServerInfo.dwTimeout == rInetServerInfo.dwTimeout &&
0 == lstrcmp(m_rInetServerInfo.szUserName, rInetServerInfo.szUserName) &&
('\0' == rInetServerInfo.szPassword[0] ||
0 == lstrcmp(m_rInetServerInfo.szPassword, rInetServerInfo.szPassword)) &&
0 == lstrcmp(m_rInetServerInfo.szServerName, rInetServerInfo.szServerName) &&
0 == lstrcmp(m_rInetServerInfo.szConnectoid, rInetServerInfo.szConnectoid))
{
hr = S_OK;
goto exit;
}
fInetInit = TRUE;
m_pTransport->DropConnection();
}
hr = m_op.pCallback->CanConnect(m_szAccountId, NOFLAGS);
if (hr != S_OK)
{
if (hr == S_FALSE)
hr = HR_E_USER_CANCEL_CONNECT;
goto exit;
}
if (!m_pTransport)
{
*szLogFile = 0;
dwLog = DwGetOption(OPT_NEWS_XPORT_LOG);
if (dwLog)
{
hr = pAccount->GetPropSz(AP_ACCOUNT_NAME, szAccountName, ARRAYSIZE(szAccountName));
if (FAILED(hr))
goto exit;
_CreateDataFilePath(m_szAccountId, szAccountName, szLogFile, ARRAYSIZE(szLogFile));
}
hr = CreateNNTPTransport(&m_pTransport);
if (FAILED(hr))
goto exit;
hr = m_pTransport->InitNew(*szLogFile ? szLogFile : NULL, this);
if (FAILED(hr))
goto exit;
}
// Convert the account name to an INETSERVER struct that can be passed to Connect()
if (fInetInit)
{
CopyMemory(&m_rInetServerInfo, &rInetServerInfo, sizeof(INETSERVER));
}
else
{
hr = m_pTransport->InetServerFromAccount(pAccount, &m_rInetServerInfo);
if (FAILED(hr))
goto exit;
}
// Always connect using the most recently supplied password from the user
GetPassword(m_rInetServerInfo.dwPort, m_rInetServerInfo.szServerName,
m_rInetServerInfo.szUserName, m_rInetServerInfo.szPassword,
sizeof(m_rInetServerInfo.szPassword));
if (m_pTransport)
{
hr = m_pTransport->Connect(&m_rInetServerInfo, TRUE, TRUE);
if (hr == S_OK)
{
m_op.nsPending = NS_CONNECT;
hr = E_PENDING;
}
}
exit:
if (pAccount)
pAccount->Release();
}
else
hr = E_FAIL;
return hr;
}
HRESULT CNewsStore::Group()
{
HRESULT hr;
FOLDERINFO info;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
hr = m_pStore->GetFolderInfo(m_op.idFolder, &info);
if (SUCCEEDED(hr))
{
hr = m_pTransport->CommandGROUP(info.pszName);
if (hr == S_OK)
{
m_op.pszGroup = PszDup(info.pszName);
m_op.nsPending = NS_GROUP;
hr = E_PENDING;
}
m_pStore->FreeRecord(&info);
}
return hr;
}
HRESULT CNewsStore::GroupIfNecessary()
{
FOLDERINFO info;
HRESULT hr = S_OK;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
if (0 == (m_op.dwFlags & OPFLAG_NOGROUPCMD))
{
hr = m_pStore->GetFolderInfo(m_op.idFolder, &info);
if (SUCCEEDED(hr))
{
if (0 != lstrcmpi(m_szGroup, info.pszName))
{
hr = m_pTransport->CommandGROUP(info.pszName);
if (hr == S_OK)
{
m_op.nsPending = NS_GROUP;
hr = E_PENDING;
}
}
m_pStore->FreeRecord(&info);
}
}
return hr;
}
HRESULT CNewsStore::ExpireHeaders()
{
HRESULT hr;
FOLDERINFO info;
MESSAGEINFO Message;
DWORD dwLow, cid, cidBuf;
MESSAGEIDLIST idList;
HROWSET hRowset;
HLOCK hNotify;
hr = m_pStore->GetFolderInfo(m_op.idFolder, &info);
if (FAILED(hr))
return(hr);
dwLow = min(info.dwServerLow - 1, info.dwClientHigh);
m_pStore->FreeRecord(&info);
hr = m_pFolder->CreateRowset(IINDEX_PRIMARY, NOFLAGS, &hRowset);
if (FAILED(hr))
return(hr);
cid = 0;
cidBuf = 0;
idList.cAllocated = 0;
idList.prgidMsg = NULL;
hr = m_pFolder->LockNotify(NOFLAGS, &hNotify);
if (SUCCEEDED(hr))
{
while (TRUE)
{
hr = m_pFolder->QueryRowset(hRowset, 1, (LPVOID *)&Message, NULL);
if (FAILED(hr))
break;
// Done
if (S_FALSE == hr)
{
hr = S_OK;
break;
}
if ((DWORD_PTR)Message.idMessage <= dwLow ||
!!(Message.dwFlags & ARF_ARTICLE_EXPIRED))
{
if (cid == cidBuf)
{
cidBuf += 512;
if (!MemRealloc((void **)&idList.prgidMsg, cidBuf * sizeof(MESSAGEID)))
{
m_pFolder->FreeRecord(&Message);
hr = E_OUTOFMEMORY;
break;
}
}
idList.prgidMsg[cid] = Message.idMessage;
cid++;
}
m_pFolder->FreeRecord(&Message);
}
m_pFolder->UnlockNotify(&hNotify);
}
m_pFolder->CloseRowset(&hRowset);
// if it fails, its no big deal, they'll just have some stale headers until next time
if (cid > 0)
{
Assert(idList.prgidMsg != NULL);
idList.cMsgs = cid;
// Delete the messages from the folder without a trashcan (after all, this is news)
if (SUCCEEDED(m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, &idList, NULL, NULL)) &&
SUCCEEDED(m_pStore->GetFolderInfo(m_op.idFolder, &info)))
{
info.dwClientLow = dwLow + 1;
m_pStore->UpdateRecord(&info);
m_pStore->FreeRecord(&info);
}
MemFree(idList.prgidMsg);
}
return(hr);
}
HRESULT CNewsStore::Headers(void)
{
FOLDERINFO FolderInfo;
HRESULT hr;
RANGE rRange;
BOOL fNew;
hr = m_pStore->GetFolderInfo(m_op.idFolder, &FolderInfo);
if (FAILED(hr))
return(hr);
Assert(0 == lstrcmpi(m_szGroup, FolderInfo.pszName));
hr = _ComputeHeaderRange(m_op.dwSyncFlags, m_op.cHeaders, &FolderInfo, &rRange);
if (hr == S_OK)
{
// Transport will not allow dwFirst to be 0.
// In this case, there are no messages to be received.
Assert(rRange.dwFirst > 0);
Assert(rRange.dwFirst <= rRange.dwLast);
hr = m_pTransport->GetHeaders(&rRange);
if (hr == S_OK)
{
m_op.nsPending = NS_HEADERS;
hr = E_PENDING;
}
}
if (hr != E_PENDING)
{
if (m_pROP != NULL)
{
MemFree(m_pROP);
m_pROP = NULL;
}
}
if (hr == S_FALSE)
hr = S_OK;
m_pStore->FreeRecord(&FolderInfo);
return(hr);
}
HRESULT CNewsStore::_ComputeHeaderRange(SYNCFOLDERFLAGS dwFlags, DWORD cHeaders, FOLDERINFO *pInfo, RANGE *pRange)
{
HRESULT hr;
UINT uLeftToGet;
ULONG ulMaxReq;
BOOL fFullScan;
DWORD dwDownload;
CRangeList *pRequested;
Assert(pInfo != NULL);
Assert(pRange != NULL);
// Bail if there are no messages to be gotten
if (0 == pInfo->dwServerCount ||
pInfo->dwServerLow > pInfo->dwServerHigh)
{
Assert(!m_pROP);
return(S_FALSE);
}
pRequested = new CRangeList;
if (pRequested == NULL)
{
hr = E_OUTOFMEMORY;
goto error;
}
if (pInfo->Requested.cbSize > 0)
pRequested->Load(pInfo->Requested.pBlobData, pInfo->Requested.cbSize);
ulMaxReq = pRequested->Max();
Assert(0 == pRequested->Min());
fFullScan = (0 == pRequested->MinOfRange(ulMaxReq));
// Bail if we've scanned the whole group
if (fFullScan && (pRequested->Max() == pInfo->dwServerHigh))
goto endit;
if (m_pROP != NULL)
{
// Bail if we've gotten all the user wants
if (m_pROP->uObtained >= ((FRACNEEDED * m_pROP->dwChunk) / 10))
goto endit;
// Bail if this has gone on for too many calls
if (m_pROP->cOps > m_pROP->MaxOps)
goto endit;
}
else
{
m_op.dwProgress = 0;
// Do setup
if (!MemAlloc((LPVOID*)&m_pROP, sizeof(SREFRESHOP)))
{
hr = E_OUTOFMEMORY;
goto error;
}
ZeroMemory(m_pROP, sizeof(SREFRESHOP));
m_pROP->fOnlyNewHeaders = !!(dwFlags & SYNC_FOLDER_NEW_HEADERS);
if (!!(dwFlags & SYNC_FOLDER_XXX_HEADERS))
{
Assert(cHeaders > 0);
m_pROP->dwChunk = cHeaders;
m_pROP->dwDlSize = (DWORD)((m_pROP->dwChunk * DLOVERKILL) / 10);
m_pROP->MaxOps = MAXOPS;
m_pROP->fEnabled = TRUE;
m_op.dwTotal = m_pROP->dwDlSize;
}
else
{
// user has turned off the X headers option
// so we need to get all of the newest headers, but then also
// grab any old headers on this refresh
// we have to do all that here and now because there is no
// UI available to the user
// don't want to quit except on a full scan
// m_pROP->fOnlyNewHeaders = FALSE;
m_pROP->MaxOps = m_pROP->dwChunk = m_pROP->dwDlSize = pInfo->dwServerHigh;
Assert(!m_pROP->fEnabled);
m_op.dwTotal = pInfo->dwNotDownloaded;
}
}
uLeftToGet = m_pROP->dwDlSize - m_pROP->uObtained;
if (RANGE_ERROR == ulMaxReq)
{
AssertSz(0, TEXT("shouldn't be here, but you can ignore."));
ulMaxReq = pInfo->dwServerLow - 1;
}
Assert(ulMaxReq <= pInfo->dwServerHigh);
Assert(pRequested->IsInRange(pInfo->dwServerLow - 1));
///////////////////////////////////
/// Compute begin and end numbers
if (ulMaxReq < pInfo->dwServerHigh)
{
// get the newest headers
Assert(0 == m_pROP->cOps); // EricAn said this assert might not be valid
Assert(ulMaxReq + 1 >= pInfo->dwServerLow);
m_pROP->dwLast = pInfo->dwServerHigh;
if (!m_pROP->fEnabled || (m_pROP->dwChunk - 1 > m_pROP->dwLast))
{
m_pROP->dwFirst = ulMaxReq + 1;
}
else
{
// we use dwChunk here b/c headers will be nearly dense
m_pROP->dwFirst = max(m_pROP->dwLast - (m_pROP->dwChunk - 1), ulMaxReq + 1);
}
m_pROP->dwFirstNew = ulMaxReq + 1;
}
else if (m_pROP->dwFirst > m_pROP->dwFirstNew) // if init to zero, won't be true
{
// still new headers user hasn't seen
Assert(m_pROP->cOps); // can't happen at first
Assert(m_pROP->dwFirstNew >= pInfo->dwServerLow); // better be valid
Assert(m_pROP->fEnabled); // should have gotten them all
m_pROP->dwLast = m_pROP->dwFirst - 1; // since cOps is pos, dwFirst is valid
if (uLeftToGet - 1 > m_pROP->dwLast)
m_pROP->dwFirst = m_pROP->dwFirstNew;
else
m_pROP->dwFirst = max(m_pROP->dwLast - (uLeftToGet - 1), m_pROP->dwFirstNew);
}
else if (!m_pROP->fOnlyNewHeaders)
{
RangeType rt;
// want to find the highest num header we've never requested
m_pROP->dwFirstNew = pInfo->dwServerHigh; // no new mesgs in this session
if (!pRequested->HighestAntiRange(&rt))
{
AssertSz(0, TEXT("You can ignore if you want, but we shouldn't be here."));
rt.low = max(pRequested->Max() + 1, pInfo->dwServerLow);
rt.high = pInfo->dwServerHigh;
if (rt.low == rt.high)
goto endit;
}
m_pROP->dwLast = rt.high;
if (!m_pROP->fEnabled || ((uLeftToGet - 1) > rt.high))
m_pROP->dwFirst = rt.low;
else
m_pROP->dwFirst = max(rt.low, rt.high - (uLeftToGet - 1));
}
else
{
goto endit;
}
// check our math and logic about download range
Assert(m_pROP->dwLast <= pInfo->dwServerHigh);
Assert(m_pROP->dwFirst >= pInfo->dwServerLow);
Assert(!pRequested->IsInRange(m_pROP->dwLast));
Assert(!pRequested->IsInRange(m_pROP->dwFirst));
Assert(!m_pROP->fEnabled || ((m_pROP->dwLast - m_pROP->dwFirst) < m_pROP->dwDlSize));
if (!m_pROP->dwLast || (m_pROP->dwFirst > m_pROP->dwLast))
{
AssertSz(0, TEXT("You would have made a zero size HEADER call"));
goto endit;
}
pRequested->Release();
pRange->idType = RT_RANGE;
pRange->dwFirst = m_pROP->dwFirst;
pRange->dwLast = m_pROP->dwLast;
dwDownload = pRange->dwLast - pRange->dwFirst + 1;
if (dwDownload + m_op.dwProgress > m_op.dwTotal)
m_op.dwTotal = dwDownload + m_op.dwProgress;
return(S_OK);
endit:
hr = S_FALSE;
error:
if (m_pROP != NULL)
{
MemFree(m_pROP);
m_pROP = NULL;
}
if(pRequested) pRequested->Release();
return(hr);
}
HRESULT CNewsStore::Article()
{
HRESULT hr;
MESSAGEINFO info;
DWORD dwTotalLines;
ARTICLEID rArticleId;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
dwTotalLines = 0;
if (m_op.pszArticleId != NULL)
{
rArticleId.idType = AID_MSGID;
rArticleId.pszMessageId = m_op.pszArticleId;
m_op.pszArticleId = NULL;
}
else if (m_op.idMessage)
{
ZeroMemory(&info, sizeof(info));
info.idMessage = m_op.idMessage;
hr = m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &info, NULL);
if (DB_S_FOUND == hr)
{
dwTotalLines = info.cLines;
m_pFolder->FreeRecord(&info);
}
rArticleId.idType = AID_ARTICLENUM;
rArticleId.dwArticleNum = (DWORD_PTR)m_op.idMessage;
}
else
{
Assert(m_op.idServerMessage);
rArticleId.idType = AID_ARTICLENUM;
rArticleId.dwArticleNum = m_op.idServerMessage;
}
m_op.dwProgress = 0;
m_op.dwTotal = dwTotalLines;
hr = m_pTransport->CommandARTICLE(&rArticleId);
if (hr == S_OK)
{
m_op.nsPending = NS_ARTICLE;
hr = E_PENDING;
}
return(hr);
}
HRESULT CNewsStore::Post()
{
HRESULT hr;
NNTPMESSAGE rMsg;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
rMsg.pstmMsg = m_op.pStream;
rMsg.cbSize = 0;
hr = m_pTransport->CommandPOST(&rMsg);
if (SUCCEEDED(hr))
{
m_op.nsPending = NS_POST;
hr = E_PENDING;
}
return(hr);
}
HRESULT CNewsStore::NewGroups()
{
HRESULT hr;
NNTPMESSAGE rMsg;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
hr = m_pTransport->CommandNEWGROUPS(&m_op.st, NULL);
if (SUCCEEDED(hr))
{
m_op.nsPending = NS_NEWGROUPS;
hr = E_PENDING;
}
return(hr);
}
int __cdecl CompareFolderIds(const void *elem1, const void *elem2)
{
return(*((DWORD *)elem1) - *((DWORD *)elem2));
}
HRESULT CNewsStore::List()
{
HRESULT hr;
ULONG cFolders;
FOLDERINFO info;
IEnumerateFolders *pEnum;
Assert(0 == m_op.dwFlags);
Assert(m_op.pPrevFolders == NULL);
m_op.cPrevFolders = 0;
hr = m_pStore->EnumChildren(m_idParent, FALSE, &pEnum);
if (SUCCEEDED(hr))
{
hr = pEnum->Count(&cFolders);
if (SUCCEEDED(hr) && cFolders > 0)
{
if (!MemAlloc((void **)&m_op.pPrevFolders, cFolders * sizeof(FOLDERID)))
{
hr = E_OUTOFMEMORY;
}
else
{
while (S_OK == pEnum->Next(1, &info, NULL))
{
m_op.pPrevFolders[m_op.cPrevFolders] = info.idFolder;
m_op.cPrevFolders++;
m_pStore->FreeRecord(&info);
}
Assert(m_op.cPrevFolders == cFolders);
qsort(m_op.pPrevFolders, m_op.cPrevFolders, sizeof(FOLDERID), CompareFolderIds);
}
}
pEnum->Release();
}
if (SUCCEEDED(hr))
hr = _List(NULL);
return(hr);
}
HRESULT CNewsStore::DeleteDeadGroups()
{
ULONG i;
HRESULT hr;
FOLDERID *pId;
if (m_op.pPrevFolders != NULL)
{
Assert(m_op.cPrevFolders > 0);
for (i = 0, pId = m_op.pPrevFolders; i < m_op.cPrevFolders; i++, pId++)
{
if (*pId != 0)
{
hr = m_pStore->DeleteFolder(*pId, DELETE_FOLDER_NOTRASHCAN, NULL);
Assert(SUCCEEDED(hr));
}
}
MemFree(m_op.pPrevFolders);
m_op.pPrevFolders = NULL;
}
return(S_OK);
}
static const char c_szNewsgroups[] = "NEWSGROUPS";
HRESULT CNewsStore::Descriptions()
{
HRESULT hr;
m_op.dwFlags = OPFLAG_DESCRIPTIONS;
hr = _List(c_szNewsgroups);
return(hr);
}
HRESULT CNewsStore::_List(LPCSTR pszCommand)
{
HRESULT hr;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
m_op.dwProgress = 0;
m_op.dwTotal = 0;
hr = m_pTransport->CommandLIST((LPSTR)pszCommand);
if (hr == S_OK)
{
m_op.nsPending = NS_LIST;
hr = E_PENDING;
}
return(hr);
}
HRESULT CNewsStore::_DoOperation()
{
HRESULT hr;
STOREOPERATIONINFO soi;
STOREOPERATIONINFO *psoi;
Assert(m_op.tyOperation != SOT_INVALID);
Assert(m_op.pfnState != NULL);
Assert(m_op.cState > 0);
Assert(m_op.iState <= m_op.cState);
hr = S_OK;
if (m_op.iState == 0)
{
if (m_op.tyOperation == SOT_GET_MESSAGE)
{
// provide message id on get message start
soi.cbSize = sizeof(STOREOPERATIONINFO);
soi.idMessage = m_op.idMessage;
psoi = &soi;
}
else
{
psoi = NULL;
}
m_op.pCallback->OnBegin(m_op.tyOperation, psoi, (IOperationCancel *)this);
}
while (m_op.iState < m_op.cState)
{
hr = (this->*(m_op.pfnState[m_op.iState]))();
if (FAILED(hr))
break;
m_op.iState++;
}
if ((m_op.iState == m_op.cState) ||
(FAILED(hr) && hr != E_PENDING))
{
if (hr == HR_E_USER_CANCEL_CONNECT)
{
// if operation is canceled, add the flush flag
m_op.error.dwFlags |= SE_FLAG_FLUSHALL;
}
if (FAILED(hr))
{
IXPRESULT rIxpResult;
// Fake an IXPRESULT
ZeroMemory(&rIxpResult, sizeof(rIxpResult));
rIxpResult.hrResult = hr;
// Return meaningful error information
_FillStoreError(&m_op.error, &rIxpResult);
Assert(m_op.error.hrResult == hr);
}
else
m_op.error.hrResult = hr;
m_op.pCallback->OnComplete(m_op.tyOperation, hr, NULL, &m_op.error);
_FreeOperation();
}
return(hr);
}
//
// FUNCTION: CNewsStore::SynchronizeFolder()
//
// PURPOSE: Load all of the new messages headers for this folder
// as appropriate based on the flags
//
// PARAMETERS:
// [in] dwFlags -
//
HRESULT CNewsStore::SynchronizeFolder(SYNCFOLDERFLAGS dwFlags, DWORD cHeaders,
IStoreCallback *pCallback)
{
HRESULT hr;
// Stack
TraceCall("CNewsStore::SynchronizeFolder");
AssertSingleThreaded;
Assert(pCallback != NULL);
Assert(m_op.tyOperation == SOT_INVALID);
Assert(m_pROP == NULL);
m_op.tyOperation = SOT_SYNC_FOLDER;
m_op.pfnState = c_rgpfnSyncFolder;
m_op.iState = 0;
m_op.cState = ARRAYSIZE(c_rgpfnSyncFolder);
m_op.pCallback = pCallback;
m_op.pCallback->AddRef();
m_op.idFolder = m_idFolder;
m_op.dwSyncFlags = dwFlags;
m_op.cHeaders = cHeaders;
hr = _BeginDeferredOperation();
return hr;
}
//
// FUNCTION: CNewsStore::GetMessage()
//
// PURPOSE: Start the retrieval of a single message as specified
// by idMessage.
//
// PARAMETERS:
// [in] idFolder -
// [in] idMessage -
// [in] pStream -
// [in] pCallback - callbacks in case we need to present ui, progress,
//
HRESULT CNewsStore::GetMessage(MESSAGEID idMessage, IStoreCallback *pCallback)
{
HRESULT hr;
// Stack
TraceCall("CNewsStore::GetMessage");
AssertSingleThreaded;
Assert(pCallback != NULL);
Assert(m_op.tyOperation == SOT_INVALID);
// create a persistent stream
if (FAILED(hr = CreatePersistentWriteStream(m_pFolder, &m_op.pStream, &m_op.faStream)))
goto exit;
m_op.tyOperation = SOT_GET_MESSAGE;
m_op.pfnState = c_rgpfnGetMessage;
m_op.iState = 0;
m_op.cState = ARRAYSIZE(c_rgpfnGetMessage);
m_op.dwFlags = 0;
m_op.pCallback = pCallback;
m_op.pCallback->AddRef();
m_op.idFolder = m_idFolder;
m_op.idMessage = idMessage;
hr = _BeginDeferredOperation();
exit:
return hr;
}
HRESULT CNewsStore::GetArticle(LPCSTR pszArticleId, IStream *pStream,
IStoreCallback *pCallback)
{
HRESULT hr;
// Stack
TraceCall("CNewsStore::GetArticle");
AssertSingleThreaded;
Assert(pStream != NULL);
Assert(pCallback != NULL);
Assert(m_op.tyOperation == SOT_INVALID);
m_op.pszArticleId = PszDup(pszArticleId);
if (m_op.pszArticleId == NULL)
return(E_OUTOFMEMORY);
m_op.tyOperation = SOT_GET_MESSAGE;
m_op.pfnState = c_rgpfnGetMessage;
m_op.iState = 0;
m_op.cState = ARRAYSIZE(c_rgpfnGetMessage);
m_op.dwFlags = OPFLAG_NOGROUPCMD;
m_op.pCallback = pCallback;
m_op.pCallback->AddRef();
m_op.idFolder = m_idFolder;
m_op.idMessage = 0;
m_op.pStream = pStream;
m_op.pStream->AddRef();
hr = _BeginDeferredOperation();
return hr;
}
//
// FUNCTION: CNewsStore::PutMessage()
//
// PURPOSE: Posting news messages
//
// PARAMETERS:
// [in] idFolder -
// [in] dwFlags -
// [in] pftReceived -
// [in] pStream -
// [in] pCallback - callbacks in case we need to present ui, progress,
//
HRESULT CNewsStore::PutMessage(FOLDERID idFolder, MESSAGEFLAGS dwFlags,
LPFILETIME pftReceived, IStream *pStream,
IStoreCallback *pCallback)
{
HRESULT hr;
// Stack
TraceCall("CNewsStore::GetMessage");
AssertSingleThreaded;
Assert(pStream != NULL);
Assert(pCallback != NULL);
Assert(m_op.tyOperation == SOT_INVALID);
m_op.tyOperation = SOT_PUT_MESSAGE;
m_op.pfnState = c_rgpfnPutMessage;
m_op.iState = 0;
m_op.cState = ARRAYSIZE(c_rgpfnPutMessage);
m_op.pCallback = pCallback;
m_op.pCallback->AddRef();
m_op.idFolder = idFolder;
m_op.dwMsgFlags = dwFlags;
m_op.pStream = pStream;
m_op.pStream->AddRef();
hr = _BeginDeferredOperation();
return hr;
}
//
// FUNCTION: CNewsStore::SynchronizeStore()
//
// PURPOSE: Synchronize the list of mail groups
//
// PARAMETERS:
// [in] idParent -
// [in] dwFlags -
// [in] pCallback - callbacks in case we need to present ui, progress,
//
HRESULT CNewsStore::SynchronizeStore(FOLDERID idParent, SYNCSTOREFLAGS dwFlags, IStoreCallback *pCallback)
{
HRESULT hr;
AssertSingleThreaded;
Assert(pCallback != NULL);
Assert(m_op.tyOperation == SOT_INVALID);
m_op.tyOperation = SOT_SYNCING_STORE;
m_op.pfnState = c_rgpfnSyncStore;
m_op.iState = 0;
m_op.cState = ARRAYSIZE(c_rgpfnSyncStore);
if (0 == (dwFlags & SYNC_STORE_GET_DESCRIPTIONS))
{
// we don't need to do the descriptions command
m_op.cState -= 1;
}
m_op.pCallback = pCallback;
m_op.pCallback->AddRef();
m_op.idFolder = idParent;
hr = _BeginDeferredOperation();
return(hr);
}
HRESULT CNewsStore::GetNewGroups(LPSYSTEMTIME pSysTime, IStoreCallback *pCallback)
{
HRESULT hr;
AssertSingleThreaded;
Assert(pSysTime != NULL);
Assert(pCallback != NULL);
Assert(m_op.tyOperation == SOT_INVALID);
m_op.tyOperation = SOT_GET_NEW_GROUPS;
m_op.pfnState = c_rgpfnGetNewGroups;
m_op.iState = 0;
m_op.cState = ARRAYSIZE(c_rgpfnGetNewGroups);
m_op.pCallback = pCallback;
m_op.pCallback->AddRef();
m_op.st = *pSysTime;
m_op.idFolder = m_idParent;
hr = _BeginDeferredOperation();
return(hr);
}
//
// FUNCTION: CNewsStore::GetFolderCounts()
//
// PURPOSE: Update folder statistics for the passed in folder
//
// PARAMETERS:
// [in] idFolder - folder id associated with the newsgroup
// [in] pCallback - callbacks to send OnComplete to.
//
HRESULT CNewsStore::GetFolderCounts(FOLDERID idFolder, IStoreCallback *pCallback)
{
HRESULT hr;
// Stack
TraceCall("CNewsStore::GetFolderCounts");
AssertSingleThreaded;
Assert(pCallback != NULL);
Assert(m_op.tyOperation == SOT_INVALID);
m_op.tyOperation = SOT_UPDATE_FOLDER;
m_op.pfnState = c_rgpfnUpdateFolder;
m_op.iState = 0;
m_op.cState = ARRAYSIZE(c_rgpfnUpdateFolder);
m_op.pCallback = pCallback;
m_op.pCallback->AddRef();
m_op.idFolder = idFolder;
hr = _BeginDeferredOperation();
return hr;
}
HRESULT CNewsStore::SetIdleCallback(IStoreCallback *pDefaultCallback)
{
// Stack
TraceCall("CNewsStore::SetIdleCallback");
return E_NOTIMPL;
}
HRESULT CNewsStore::CopyMessages(IMessageFolder *pDest, COPYMESSAGEFLAGS dwOptions,
LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags,
IStoreCallback *pCallback)
{
// Stack
TraceCall("CNewsStore::CopyMessages");
return E_NOTIMPL;
}
HRESULT CNewsStore::DeleteMessages(DELETEMESSAGEFLAGS dwOptions,
LPMESSAGEIDLIST pList, IStoreCallback *pCallback)
{
// Stack
TraceCall("CNewsStore::DeleteMessages");
AssertSingleThreaded;
Assert(pList != NULL);
Assert(pCallback != NULL);
Assert(m_pFolder != NULL);
return(m_pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | dwOptions, pList, NULL, NULL));
}
HRESULT CNewsStore::SetMessageFlags(LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags, SETMESSAGEFLAGSFLAGS dwFlags,
IStoreCallback *pCallback)
{
// Stack
TraceCall("CNewsStore::SetMessageFlags");
Assert(NULL == pList || pList->cMsgs > 0);
return E_NOTIMPL;
}
HRESULT CNewsStore::GetServerMessageFlags(MESSAGEFLAGS *pFlags)
{
return S_FALSE;
}
HRESULT CNewsStore::CreateFolder(FOLDERID idParent, SPECIALFOLDER tySpecial,
LPCSTR pszName, FLDRFLAGS dwFlags,
IStoreCallback *pCallback)
{
// Stack
TraceCall("CNewsStore::CreateFolder");
return E_NOTIMPL;
}
HRESULT CNewsStore::MoveFolder(FOLDERID idFolder, FOLDERID idParentNew,
IStoreCallback *pCallback)
{
// Stack
TraceCall("CNewsStore::MoveFolder");
return E_NOTIMPL;
}
HRESULT CNewsStore::RenameFolder(FOLDERID idFolder, LPCSTR pszName, IStoreCallback *pCallback)
{
// Stack
TraceCall("CNewsStore::RenameFolder");
return E_NOTIMPL;
}
HRESULT CNewsStore::DeleteFolder(FOLDERID idFolder, DELETEFOLDERFLAGS dwFlags, IStoreCallback *pCallback)
{
// Stack
TraceCall("CNewsStore::DeleteFolder");
return E_NOTIMPL;
}
HRESULT CNewsStore::SubscribeToFolder(FOLDERID idFolder, BOOL fSubscribe,
IStoreCallback *pCallback)
{
// Stack
TraceCall("CNewsStore::SubscribeToFolder");
return E_NOTIMPL;
}
//
// FUNCTION: CNewsStore::Cancel()
//
// PURPOSE: Cancel the operation
//
// PARAMETERS:
// [in] tyCancel - The way that the operation was canceled.
// Generally CT_ABORT or CT_CANCEL
//
HRESULT CNewsStore::Cancel(CANCELTYPE tyCancel)
{
if (m_op.tyOperation != SOT_INVALID)
{
m_op.fCancel = TRUE;
if (_FConnected())
m_pTransport->DropConnection();
}
return(S_OK);
}
//
// FUNCTION: CNewsStore::OnTimeout()
//
// PURPOSE:
//
// PARAMETERS:
// [in] pdwTimeout -
// [in] pTransport -
//
HRESULT CNewsStore::OnTimeout(DWORD *pdwTimeout, IInternetTransport *pTransport)
{
// Stack
TraceCall("CNewsStore::OnTimeout");
AssertSingleThreaded;
Assert(m_op.tyOperation != SOT_INVALID);
Assert(m_op.pCallback != NULL);
m_op.pCallback->OnTimeout(&m_rInetServerInfo, pdwTimeout, IXP_NNTP);
return(S_OK);
}
//
// FUNCTION: CNewsStore::OnLogonPrompt()
//
// PURPOSE:
//
// PARAMETERS:
// [in] pInetServer -
// [in] pTransport -
//
HRESULT CNewsStore::OnLogonPrompt(LPINETSERVER pInetServer, IInternetTransport *pTransport)
{
HRESULT hr;
char szPassword[CCHMAX_PASSWORD];
// Stack
TraceCall("CNewsStore::OnLogonPrompt");
AssertSingleThreaded;
Assert(pInetServer != NULL);
Assert(pTransport != NULL);
Assert(m_op.tyOperation != SOT_INVALID);
Assert(m_op.pCallback != NULL);
// Check if we have a cached password that's different from current password
hr = GetPassword(pInetServer->dwPort, pInetServer->szServerName, pInetServer->szUserName,
szPassword, sizeof(szPassword));
if (SUCCEEDED(hr) && 0 != lstrcmp(szPassword, pInetServer->szPassword))
{
StrCpyN(pInetServer->szPassword, szPassword, ARRAYSIZE(pInetServer->szPassword));
return S_OK;
}
hr = m_op.pCallback->OnLogonPrompt(pInetServer, IXP_NNTP);
// Cache the password for this session
if (S_OK == hr)
{
SavePassword(pInetServer->dwPort, pInetServer->szServerName, pInetServer->szUserName, pInetServer->szPassword);
// copy the password/user name into our local inetserver
StrCpyN(m_rInetServerInfo.szPassword, pInetServer->szPassword, ARRAYSIZE(m_rInetServerInfo.szPassword));
StrCpyN(m_rInetServerInfo.szUserName, pInetServer->szUserName, ARRAYSIZE(m_rInetServerInfo.szUserName));
}
return(hr);
}
//
// FUNCTION: CNewsStore::OnPrompt()
//
// PURPOSE:
//
// PARAMETERS:
// [in] hrError -
// [in] pszText -
// [in] pszCaption -
// [in] uType -
// [in] pTransport -
//
int CNewsStore::OnPrompt(HRESULT hrError, LPCSTR pszText, LPCSTR pszCaption,
UINT uType, IInternetTransport *pTransport)
{
int iResponse = 0;
// Stack
TraceCall("CNewsStore::OnPrompt");
AssertSingleThreaded;
Assert(m_op.tyOperation != SOT_INVALID);
Assert(m_op.pCallback != NULL);
m_op.pCallback->OnPrompt(hrError, pszText, pszCaption, uType, &iResponse);
return(iResponse);
}
//
// FUNCTION: CNewsStore::OnStatus()
//
// PURPOSE:
//
// PARAMETERS:
// [in] ixpstatus - status code passed in from the transport
// [in] pTransport - The NNTP transport that is calling us
//
HRESULT CNewsStore::OnStatus(IXPSTATUS ixpstatus, IInternetTransport *pTransport)
{
HRESULT hr;
// Stack
TraceCall("CNewsStore::OnStatus");
AssertSingleThreaded;
m_ixpStatus = ixpstatus;
if (m_op.pCallback != NULL)
m_op.pCallback->OnProgress(SOT_CONNECTION_STATUS, ixpstatus, 0, m_rInetServerInfo.szServerName);
// If we were disconnected, then clean up some internal state.
if (IXP_DISCONNECTED == ixpstatus)
{
// Reset the group name so we know to reissue it later.
m_szGroup[0] = 0;
if (m_op.tyOperation != SOT_INVALID)
{
Assert(m_op.pCallback != NULL);
if (m_op.fCancel)
{
IXPRESULT rIxpResult;
// if operation is canceled, add the flush flag
m_op.error.dwFlags |= SE_FLAG_FLUSHALL;
// Fake the IXPRESULT
ZeroMemory(&rIxpResult, sizeof(rIxpResult));
rIxpResult.hrResult = STORE_E_OPERATION_CANCELED;
// Return meaningful error information
_FillStoreError(&m_op.error, &rIxpResult);
Assert(STORE_E_OPERATION_CANCELED == m_op.error.hrResult);
m_op.pCallback->OnComplete(m_op.tyOperation, m_op.error.hrResult, NULL, &m_op.error);
_FreeOperation();
}
}
}
return(S_OK);
}
//
// FUNCTION: CNewsStore::OnError()
//
// PURPOSE:
//
// PARAMETERS:
// [in] ixpstatus -
// [in] pResult -
// [in] pTransport -
//
HRESULT CNewsStore::OnError(IXPSTATUS ixpstatus, LPIXPRESULT pResult,
IInternetTransport *pTransport)
{
// Stack
TraceCall("CNewsStore::OnError");
return(S_OK);
}
//
// FUNCTION: CNewsStore::OnCommand()
//
// PURPOSE:
//
// PARAMETERS:
// [in] cmdtype -
// [in] pszLine -
// [in] hrResponse -
// [in] pTransport -
//
HRESULT CNewsStore::OnCommand(CMDTYPE cmdtype, LPSTR pszLine, HRESULT hrResponse,
IInternetTransport *pTransport)
{
// Stack
TraceCall("CNewsStore::OnCommand");
return E_NOTIMPL;
}
HRESULT CNewsStore::GetParentWindow(DWORD dwReserved, HWND *phwndParent)
{
HRESULT hr;
AssertSingleThreaded;
Assert(m_op.pCallback != NULL);
hr = m_op.pCallback->GetParentWindow(dwReserved, phwndParent);
return hr;
}
HRESULT CNewsStore::GetAccount(LPDWORD pdwServerType, IImnAccount **ppAccount)
{
// Locals
HRESULT hr=S_OK;
// Invalid Args
Assert(ppAccount);
Assert(g_pAcctMan);
// Initialize
*ppAccount = NULL;
// Find the Account
IF_FAILEXIT(hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_szAccountId, ppAccount));
// Set server type
*pdwServerType = SRV_NNTP;
exit:
// Done
return(hr);
}
//
// FUNCTION: CNewsStore::OnResponse()
//
// PURPOSE:
//
// PARAMETERS:
// [in] pResponse - response data from the query
//
HRESULT CNewsStore::OnResponse(LPNNTPRESPONSE pResponse)
{
HRESULT hr, hrResult;
AssertSingleThreaded;
// If we got disconnected etc while there was still socket activity pending
// this can happen.
if (m_op.tyOperation == SOT_INVALID)
return(S_OK);
Assert(m_op.pCallback != NULL);
// Here's a little special something. If the caller is waiting for a connect
// response, and the connect fails the transport's returns a response with the
// state set to NS_DISCONNECTED. If this is the case, we coerce it a bit to
// make the states happy.
if (m_op.nsPending == NS_CONNECT && pResponse->state == NS_DISCONNECTED)
pResponse->state = NS_CONNECT;
if (pResponse->state == NS_IDLE)
return(S_OK);
// Check to see if we're in the right state. If we're out of sync, good
// luck trying to recover without disconnecting.
Assert(pResponse->state == m_op.nsPending);
hr = S_OK;
hrResult = pResponse->rIxpResult.hrResult;
// If this is a GROUP command, we need to update our internal state to show
// what group we're now in if we need to switch later. Also update the
// folderinfo with current stats from the server.
if (pResponse->state == NS_GROUP)
hr = _HandleGroupResponse(pResponse);
// We need to handle the article response to copy the lines to the caller's
// stream.
else if (pResponse->state == NS_ARTICLE)
hr = _HandleArticleResponse(pResponse);
//pump the data into the store
else if (pResponse->state == NS_LIST)
hr = _HandleListResponse(pResponse, FALSE);
//pump the headers into the folder
else if (pResponse->state == NS_HEADERS)
hr = _HandleHeadResponse(pResponse);
//callback to the poster with result
else if (pResponse->state == NS_POST)
hr = _HandlePostResponse(pResponse);
else if (pResponse->state == NS_NEWGROUPS)
hr = _HandleListResponse(pResponse, TRUE);
else if (pResponse->state == NS_XHDR)
{
if (m_fXhdrSubject)
hr = _HandleXHdrSubjectResponse(pResponse);
else
hr = _HandleXHdrReferencesResponse(pResponse);
}
else if (FAILED(pResponse->rIxpResult.hrResult))
{
Assert(pResponse->fDone);
_FillStoreError(&m_op.error, &pResponse->rIxpResult);
if (pResponse->state == NS_CONNECT)
{
// if connection fails, then add the flush-flag
m_op.error.dwFlags |= SE_FLAG_FLUSHALL;
}
m_op.pCallback->OnComplete(m_op.tyOperation, pResponse->rIxpResult.hrResult, NULL, &m_op.error);
}
pResponse->pTransport->ReleaseResponse(pResponse);
if (FAILED(hrResult))
{
_FreeOperation();
return(S_OK);
}
if (FAILED(hr))
{
m_op.error.hrResult = hr;
if (_FConnected())
m_pTransport->DropConnection();
m_op.pCallback->OnComplete(m_op.tyOperation, hr, NULL, NULL);
_FreeOperation();
return (S_OK);
}
// Check to see if we can issue the next command
else if (pResponse->fDone)
{
m_op.iState++;
_DoOperation();
}
return(S_OK);
}
//
// FUNCTION: CNewsStore::HandleHeadResponse
//
// PURPOSE: Stuff the headers into the message store
//
// PARAMETERS:
// pResp - Pointer to an NNTPResp from the server.
//
// RETURN VALUE:
// ignored
//
HRESULT CNewsStore::_HandleHeadResponse(LPNNTPRESPONSE pResp)
{
DWORD dwLow, dwHigh;
BOOL fFreeReq, fFreeRead;
HRESULT hr;
CRangeList *pRange;
LPSTR lpsz;
ADDRESSLIST addrList;
PROPVARIANT rDecoded;
NNTPHEADER *pHdrOld;
FOLDERINFO FolderInfo;
MESSAGEINFO rMessageInfo;
MESSAGEINFO *pHdrNew = &rMessageInfo;
IOERule *pIRuleSender = NULL;
BOOL fDontSave = FALSE;
HLOCK hNotifyLock = NULL;
ACT_ITEM * pActions = NULL;
ULONG cActions = 0;
IOEExecRules * pIExecRules = NULL;
Assert(m_pFolder);
Assert(pResp);
Assert(m_pROP != NULL);
if (FAILED(pResp->rIxpResult.hrResult))
{
Assert(pResp->fDone);
_FillStoreError(&m_op.error, &pResp->rIxpResult);
m_op.pCallback->OnComplete(m_op.tyOperation, pResp->rIxpResult.hrResult, NULL, &m_op.error);
if (m_pROP != NULL)
{
MemFree(m_pROP);
m_pROP = NULL;
}
return(S_OK);
}
if (pResp->rHeaders.cHeaders == 0)
{
Assert(pResp->fDone);
if (SUCCEEDED(m_pStore->GetFolderInfo(m_idFolder, &FolderInfo)))
{
AddRequestedRange(&FolderInfo, m_pROP->dwFirst, m_pROP->dwLast, &fFreeReq, &fFreeRead);
FolderInfo.dwNotDownloaded = NewsUtil_GetNotDownloadCount(&FolderInfo);
m_pStore->UpdateRecord(&FolderInfo);
if (fFreeReq)
MemFree(FolderInfo.Requested.pBlobData);
if (fFreeRead)
MemFree(FolderInfo.Read.pBlobData);
m_pROP->cOps++;
m_op.iState--;
m_pStore->FreeRecord(&FolderInfo);
}
return(S_OK);
}
pRange = NULL;
if (SUCCEEDED(m_pStore->GetFolderInfo(m_idFolder, &FolderInfo)))
{
if (FolderInfo.Read.cbSize > 0)
{
pRange = new CRangeList;
if (pRange != NULL)
pRange->Load(FolderInfo.Read.pBlobData, FolderInfo.Read.cbSize);
}
m_pStore->FreeRecord(&FolderInfo);
}
m_pROP->uObtained += pResp->rHeaders.cHeaders;
// Get the block sender rule if it exists
Assert(NULL != g_pRulesMan);
(VOID) g_pRulesMan->GetRule(RULEID_SENDERS, RULE_TYPE_NEWS, 0, &pIRuleSender);
m_pFolder->LockNotify(NOFLAGS, &hNotifyLock);
// Loop through the headers in pResp and convert each to a MESSAGEINFO
// and write it to the store
for (UINT i = 0; i < pResp->rHeaders.cHeaders; i++)
{
m_op.dwProgress++;
pHdrOld = &(pResp->rHeaders.rgHeaders[i]);
ZeroMemory(&rMessageInfo, sizeof(rMessageInfo));
fDontSave = FALSE;
// Article ID
pHdrNew->idMessage = (MESSAGEID)((DWORD_PTR)pHdrOld->dwArticleNum);
if (DB_S_FOUND == m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &rMessageInfo, NULL))
{
m_pFolder->FreeRecord(&rMessageInfo);
m_op.dwProgress++;
continue;
}
// Account ID
pHdrNew->pszAcctId = m_szAccountId;
pHdrNew->pszAcctName = m_rInetServerInfo.szAccount;
// Subject
rDecoded.vt = VT_LPSTR;
if (FAILED(MimeOleDecodeHeader(NULL, pHdrOld->pszSubject, &rDecoded, NULL)))
pHdrNew->pszSubject = PszDup(pHdrOld->pszSubject);
else
pHdrNew->pszSubject = rDecoded.pszVal;
// Strip trailing whitespace from the subject
ULONG cb = 0;
UlStripWhitespace(pHdrNew->pszSubject, FALSE, TRUE, &cb);
// Normalize the subject
pHdrNew->pszNormalSubj = SzNormalizeSubject(pHdrNew->pszSubject);
// From
pHdrNew->pszFromHeader = pHdrOld->pszFrom;
if (S_OK == MimeOleParseRfc822Address(IAT_FROM, IET_ENCODED, pHdrNew->pszFromHeader, &addrList))
{
if (addrList.cAdrs > 0)
{
pHdrNew->pszDisplayFrom = addrList.prgAdr[0].pszFriendly;
addrList.prgAdr[0].pszFriendly = NULL;
pHdrNew->pszEmailFrom = addrList.prgAdr[0].pszEmail;
addrList.prgAdr[0].pszEmail = NULL;
}
g_pMoleAlloc->FreeAddressList(&addrList);
}
// Date
MimeOleInetDateToFileTime(pHdrOld->pszDate, &pHdrNew->ftSent);
// Set the Reveived date (this will get set right when we download the message)
pHdrNew->ftReceived = pHdrNew->ftSent;
// Message-ID
pHdrNew->pszMessageId = pHdrOld->pszMessageId;
// References
pHdrNew->pszReferences = pHdrOld->pszReferences;
// Article Size in bytes
pHdrNew->cbMessage = pHdrOld->dwBytes;
// Lines
pHdrNew->cLines = pHdrOld->dwLines;
// XRef
if (pHdrOld->pszXref)
pHdrNew->pszXref = pHdrOld->pszXref;
else
pHdrNew->pszXref = NULL;
// Its a news message
FLAGSET(pHdrNew->dwFlags, ARF_NEWSMSG);
if (NULL != pIRuleSender)
{
pIRuleSender->Evaluate(pHdrNew->pszAcctId, pHdrNew, m_pFolder,
NULL, NULL, pHdrOld->dwBytes, &pActions, &cActions);
if ((1 == cActions) && (ACT_TYPE_DELETE == pActions[0].type))
{
fDontSave = TRUE;
}
}
//Add it to the database
hr = S_OK;
if (FALSE == fDontSave)
{
if (pRange != NULL)
{
if (pRange->IsInRange(pHdrOld->dwArticleNum))
FLAGSET(pHdrNew->dwFlags, ARF_READ);
}
hr = m_pFolder->InsertRecord(pHdrNew);
if (SUCCEEDED(hr))
{
if (NULL == pIExecRules)
{
CExecRules * pExecRules;
pExecRules = new CExecRules;
if (NULL != pExecRules)
{
hr = pExecRules->QueryInterface(IID_IOEExecRules, (void **) &pIExecRules);
if (FAILED(hr))
{
delete pExecRules;
}
}
}
g_pRulesMan->ExecuteRules(RULE_TYPE_NEWS, NOFLAGS, NULL, pIExecRules, pHdrNew, m_pFolder, NULL);
}
}
// Free the memory in rMessageInfo so we can start anew with the next entry
SafeMemFree(pHdrNew->pszSubject);
SafeMemFree(pHdrNew->pszDisplayFrom);
SafeMemFree(pHdrNew->pszEmailFrom);
// Free up any actions done by rules
if (NULL != pActions)
{
RuleUtil_HrFreeActionsItem(pActions, cActions);
MemFree(pActions);
pActions = NULL;
}
if (FAILED(hr) && hr != DB_E_DUPLICATE)
{
SafeRelease(pRange);
SafeRelease(pIRuleSender);
SafeRelease(pIExecRules);
m_pFolder->UnlockNotify(&hNotifyLock);
return(hr);
}
m_op.pCallback->OnProgress(SOT_SYNC_FOLDER, m_op.dwProgress, m_op.dwTotal, m_szGroup);
}
m_pFolder->UnlockNotify(&hNotifyLock);
SafeRelease(pIRuleSender);
SafeRelease(pIExecRules);
SafeRelease(pRange);
Assert(m_op.dwProgress <= m_op.dwTotal);
if (m_op.pCallback)
{
m_op.pCallback->OnProgress(SOT_SYNC_FOLDER, m_op.dwProgress, m_op.dwTotal, m_szGroup);
// We have to re-fetch the folder info because m_pFolder->InsertRecord may have update this folder....
if (m_pStore && SUCCEEDED(m_pStore->GetFolderInfo(m_idFolder, &FolderInfo)))
{
dwLow = pResp->rHeaders.rgHeaders[0].dwArticleNum;
dwHigh = pResp->rHeaders.rgHeaders[pResp->rHeaders.cHeaders - 1].dwArticleNum;
AddRequestedRange(&FolderInfo, m_pROP->dwFirst, pResp->fDone ? m_pROP->dwLast : dwHigh, &fFreeReq, &fFreeRead);
if (FolderInfo.dwClientLow == 0 || dwLow < FolderInfo.dwClientLow)
FolderInfo.dwClientLow = dwLow;
if (dwHigh > FolderInfo.dwClientHigh)
FolderInfo.dwClientHigh = dwHigh;
FolderInfo.dwNotDownloaded = NewsUtil_GetNotDownloadCount(&FolderInfo);
m_pStore->UpdateRecord(&FolderInfo);
if (fFreeReq)
MemFree(FolderInfo.Requested.pBlobData);
if (fFreeRead)
MemFree(FolderInfo.Read.pBlobData);
if (pResp->fDone)
{
m_pROP->cOps++;
m_op.iState--;
}
m_pStore->FreeRecord(&FolderInfo);
}
}
return(S_OK);
}
void MarkExistingFolder(FOLDERID idFolder, FOLDERID *pId, ULONG cId)
{
// TODO: if this linear search is too slow, use a binary search
// (but we'll have to switch to a struct with folderid and bool)
ULONG i;
Assert(pId != NULL);
Assert(cId > 0);
for (i = 0; i < cId; i++, pId++)
{
if (idFolder == *pId)
{
*pId = 0;
break;
}
else if (idFolder < *pId)
{
break;
}
}
}
//
// FUNCTION: CNewsStore::HandleListResponse
//
// PURPOSE: Callback function used by the protocol to give us one line
// at a time in response to a "LIST" command. Add each line
// as a folder in the global folder store.
//
// PARAMETERS:
// pResp - Pointer to an NNTPResp from the server.
//
// RETURN VALUE:
// ignored
//
HRESULT CNewsStore::_HandleListResponse(LPNNTPRESPONSE pResp, BOOL fNew)
{
LPSTR psz, pszCount;
int nSize;
char szGroupName[CCHMAX_FOLDER_NAME], szNumber[15];
FLDRFLAGS fFolderFlags;
HRESULT hr;
BOOL fDescriptions;
UINT lFirst, lLast;
FOLDERINFO Folder;
STOREOPERATIONTYPE type;
LPNNTPLIST pnl = &pResp->rList;
Assert(pResp);
if (FAILED(pResp->rIxpResult.hrResult))
{
Assert(pResp->fDone);
_FillStoreError(&m_op.error, &pResp->rIxpResult);
m_op.pCallback->OnComplete(m_op.tyOperation, pResp->rIxpResult.hrResult, NULL, &m_op.error);
return(S_OK);
}
fDescriptions = !!(m_op.dwFlags & OPFLAG_DESCRIPTIONS);
if ((fNew && pnl->cLines > 0) ||
(!fNew && pResp->fDone))
{
if (SUCCEEDED(m_pStore->GetFolderInfo(m_idParent, &Folder)))
{
if (fNew ^ !!(Folder.dwFlags & FOLDER_HASNEWGROUPS))
{
Folder.dwFlags ^= FOLDER_HASNEWGROUPS;
m_pStore->UpdateRecord(&Folder);
}
m_pStore->FreeRecord(&Folder);
}
}
for (DWORD i = 0; i < pnl->cLines; i++, m_op.dwProgress++)
{
// Parse out just the group name.
psz = pnl->rgszLines[i];
Assert(*psz);
if (fDescriptions && *psz == '#')
continue;
while (*psz && !IsSpace(psz))
psz = CharNext(psz);
nSize = (int)(psz - pnl->rgszLines[i]);
if (nSize >= CCHMAX_FOLDER_NAME)
nSize = CCHMAX_FOLDER_NAME - 1;
CopyMemory(szGroupName, pnl->rgszLines[i], nSize);
szGroupName[nSize] = 0;
// this is the first article in the group
while (*psz && IsSpace(psz))
psz = CharNext(psz);
if (fDescriptions)
{
// psz now points to the description which should be
// null terminated in the response.
// Load the folder, if possible, and set the description
// on it.
ZeroMemory(&Folder, sizeof(FOLDERINFO));
Folder.pszName = szGroupName;
Folder.idParent = m_idParent;
if (DB_S_FOUND == m_pStore->FindRecord(IINDEX_ALL, COLUMNS_ALL, &Folder, NULL))
{
if (Folder.pszDescription == NULL ||
0 != lstrcmp(psz, Folder.pszDescription))
{
Folder.pszDescription = psz;
m_pStore->UpdateRecord(&Folder);
}
m_pStore->FreeRecord(&Folder);
}
}
else
{
pszCount = psz;
while (*psz && !IsSpace(psz))
psz = CharNext(psz);
nSize = (int) (psz - pszCount);
CopyMemory(szNumber, pszCount, nSize);
szNumber[nSize] = 0;
lLast = StrToInt(szNumber);
// this is the last article in the group
while (*psz && IsSpace(psz))
psz = CharNext(psz);
pszCount = psz;
while (*psz && !IsSpace(psz))
psz = CharNext(psz);
nSize = (int)(psz - pszCount);
CopyMemory(szNumber, pszCount, nSize);
szNumber[nSize] = 0;
lFirst = StrToInt(szNumber);
// Now go see if the group allows posting or not.
while (*psz && IsSpace(psz))
psz = CharNext(psz);
#define FOLDER_LISTMASK (FOLDER_NEW | FOLDER_NOPOSTING | FOLDER_MODERATED | FOLDER_BLOCKED)
if (fNew)
fFolderFlags = FOLDER_NEW;
else
fFolderFlags = 0;
if (*psz == 'n')
fFolderFlags |= FOLDER_NOPOSTING;
else if (*psz == 'm')
fFolderFlags |= FOLDER_MODERATED;
else if (*psz == 'x')
fFolderFlags |= FOLDER_BLOCKED;
ZeroMemory(&Folder, sizeof(FOLDERINFO));
Folder.pszName = szGroupName;
Folder.idParent = m_idParent;
if (DB_S_FOUND == m_pStore->FindRecord(IINDEX_ALL, COLUMNS_ALL, &Folder, NULL))
{
if (m_op.pPrevFolders != NULL)
MarkExistingFolder(Folder.idFolder, m_op.pPrevFolders, m_op.cPrevFolders);
Assert(0 == (fFolderFlags & ~FOLDER_LISTMASK));
if ((Folder.dwFlags & FOLDER_LISTMASK) != fFolderFlags)
{
Folder.dwFlags = (Folder.dwFlags & ~FOLDER_LISTMASK);
Folder.dwFlags |= fFolderFlags;
m_pStore->UpdateRecord(&Folder);
}
// TODO: should we update server high, low and count here???
m_pStore->FreeRecord(&Folder);
}
else
{
// ZeroMemory(&Folder, sizeof(FOLDERINFO));
// Folder.idParent = m_idParent;
// Folder.pszName = szGroupName;
Folder.tySpecial = FOLDER_NOTSPECIAL;
Folder.dwFlags = fFolderFlags;
Folder.dwServerLow = lFirst;
Folder.dwServerHigh = lLast;
if (Folder.dwServerLow <= Folder.dwServerHigh)
{
Folder.dwServerCount = Folder.dwServerHigh - Folder.dwServerLow + 1;
Folder.dwNotDownloaded = Folder.dwServerCount;
}
hr = m_pStore->CreateFolder(NOFLAGS, &Folder, NULL);
Assert(hr != STORE_S_ALREADYEXISTS);
if (FAILED(hr))
return(hr);
}
}
}
// only send status every 1/2 second or so.
if (GetTickCount() > (m_dwLastStatusTicks + 500))
{
if (fNew)
type = SOT_GET_NEW_GROUPS;
else
type = fDescriptions ? SOT_SYNCING_DESCRIPTIONS : SOT_SYNCING_STORE;
m_op.pCallback->OnProgress(type, m_op.dwProgress, 0, m_rInetServerInfo.szServerName);
m_dwLastStatusTicks = GetTickCount();
}
if (!fNew &&
!fDescriptions &&
pResp->fDone &&
SUCCEEDED(pResp->rIxpResult.hrResult))
{
IImnAccount *pAcct;
SYSTEMTIME stNow;
FILETIME ftNow;
hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, m_szAccountId, &pAcct);
if (SUCCEEDED(hr))
{
GetSystemTime(&stNow);
SystemTimeToFileTime(&stNow, &ftNow);
pAcct->SetProp(AP_LAST_UPDATED, (LPBYTE)&ftNow, sizeof(ftNow));
pAcct->SaveChanges();
pAcct->Release();
}
}
return(S_OK);
}
//
// FUNCTION: CNewsStore::HandlePostResponse
//
// PURPOSE: Callback function used by the protocol to give us one line
// at a time in response to a "POST" command. Add each line
// as a folder in the global folder store.
//
// PARAMETERS:
// pResp - Pointer to an NNTPResp from the server.
//
// RETURN VALUE:
// ignored
//
HRESULT CNewsStore::_HandlePostResponse(LPNNTPRESPONSE pResp)
{
Assert(pResp != NULL);
if (FAILED(pResp->rIxpResult.hrResult))
{
Assert(pResp->fDone);
_FillStoreError(&m_op.error, &pResp->rIxpResult);
m_op.pCallback->OnComplete(m_op.tyOperation, pResp->rIxpResult.hrResult, NULL, &m_op.error);
return(S_OK);
}
return(S_OK);
}
//
// FUNCTION: CNewsStore::HandleGroupResponse
//
// PURPOSE: Callback function when a GROUP command completes
//
// PARAMETERS:
// pResp - Pointer to an NNTPResp from the server.
//
// RETURN VALUE:
// ignored
//
HRESULT CNewsStore::_HandleGroupResponse(LPNNTPRESPONSE pResp)
{
FOLDERINFO FolderInfo;
FOLDERID idFolder;
BOOL fFreeReq, fFreeRead;
Assert(pResp);
if (FAILED(pResp->rIxpResult.hrResult))
{
Assert(pResp->fDone);
Assert(m_op.pszGroup != NULL);
_FillStoreError(&m_op.error, &pResp->rIxpResult, m_op.pszGroup);
m_op.pCallback->OnComplete(m_op.tyOperation, pResp->rIxpResult.hrResult, NULL, &m_op.error);
if (pResp->rIxpResult.uiServerError == IXP_NNTP_NO_SUCH_NEWSGROUP)
{
// HACK: this is so the treeview gets the notification that this folder is being deleted
m_pStore->SubscribeToFolder(m_op.idFolder, TRUE, NULL);
m_pStore->DeleteFolder(m_op.idFolder, DELETE_FOLDER_NOTRASHCAN, NULL);
}
return(S_OK);
}
IxpAssert(pResp->rGroup.pszGroup);
StrCpyN(m_szGroup, pResp->rGroup.pszGroup, ARRAYSIZE(m_szGroup));
if (SUCCEEDED(m_pStore->GetFolderInfo(m_op.idFolder, &FolderInfo)))
{
fFreeReq = FALSE;
fFreeRead = FALSE;
if (pResp->rGroup.dwFirst <= pResp->rGroup.dwLast)
{
FolderInfo.dwServerLow = pResp->rGroup.dwFirst;
FolderInfo.dwServerHigh = pResp->rGroup.dwLast;
FolderInfo.dwServerCount = pResp->rGroup.dwCount;
if (FolderInfo.dwServerLow > 0)
AddRequestedRange(&FolderInfo, 0, FolderInfo.dwServerLow - 1, &fFreeReq, &fFreeRead);
FolderInfo.dwNotDownloaded = NewsUtil_GetNotDownloadCount(&FolderInfo);
}
else
{
FolderInfo.dwServerLow = 0;
FolderInfo.dwServerHigh = 0;
FolderInfo.dwServerCount = 0;
FolderInfo.dwNotDownloaded = 0;
}
m_pStore->UpdateRecord(&FolderInfo);
if (fFreeReq)
MemFree(FolderInfo.Requested.pBlobData);
if (fFreeRead)
MemFree(FolderInfo.Read.pBlobData);
m_pStore->FreeRecord(&FolderInfo);
}
return(S_OK);
}
//
// FUNCTION: CNewsStore::HandleArticleResponse
//
// PURPOSE: Callback function used by the protocol write a message
// into the store.
//
// PARAMETERS:
// pResp - Pointer to an NNTPResp from the server.
//
// RETURN VALUE:
// ignored
//
HRESULT CNewsStore::_HandleArticleResponse(LPNNTPRESPONSE pResp)
{
HRESULT hr;
ADJUSTFLAGS flags;
MESSAGEIDLIST list;
ULONG cbWritten;
Assert(pResp);
if (FAILED(pResp->rIxpResult.hrResult))
{
Assert(pResp->fDone);
_FillStoreError(&m_op.error, &pResp->rIxpResult);
m_op.pCallback->OnComplete(m_op.tyOperation, pResp->rIxpResult.hrResult, NULL, &m_op.error);
if ((pResp->rIxpResult.uiServerError == IXP_NNTP_NO_SUCH_ARTICLE_NUM ||
pResp->rIxpResult.uiServerError == IXP_NNTP_NO_SUCH_ARTICLE_FOUND) &&
m_pFolder != NULL)
{
list.cAllocated = 0;
list.cMsgs = 1;
list.prgidMsg = &m_op.idMessage;
flags.dwAdd = ARF_ARTICLE_EXPIRED;
flags.dwRemove = ARF_DOWNLOAD;
m_pFolder->SetMessageFlags(&list, &flags, NULL, NULL);
m_pFolder->SetMessageStream(m_op.idMessage, m_op.pStream);
m_op.faStream = 0;
}
return(S_OK);
}
// We need to write the bytes that are returned on to the stream the
// caller provided
Assert(m_op.pStream);
hr = m_op.pStream->Write(pResp->rArticle.pszLines,
pResp->rArticle.cbLines, &cbWritten);
// if (FAILED(hr) || (pResp->rArticle.cbLines != cbWritten))
if (FAILED(hr))
return(hr);
Assert(pResp->rArticle.cbLines == cbWritten);
// The NNTPRESPONSE struct is going to get sent to the caller anyway,
// so we need to doctor the cLines member to be the total line count
// for the message.
m_op.dwProgress += pResp->rArticle.cLines;
m_op.pCallback->OnProgress(SOT_GET_MESSAGE, m_op.dwProgress, m_op.dwTotal, NULL);
// If we're done, then we also rewind the stream
if (pResp->fDone)
{
HrRewindStream(m_op.pStream);
// Articles coming in from news: article urls do not have an IMessageFolder associated with them.
if (m_pFolder)
{
flags.dwAdd = 0;
flags.dwRemove = ARF_DOWNLOAD;
if (m_op.idServerMessage)
_SaveMessageToStore(m_pFolder, m_op.idServerMessage, m_op.pStream);
else
CommitMessageToStore(m_pFolder, &flags, m_op.idMessage, m_op.pStream);
m_op.faStream = 0;
}
if (m_op.pStream != NULL)
{
m_op.pStream->Release();
m_op.pStream = NULL;
}
}
SafeMemFree(pResp->rArticle.pszLines);
return(S_OK);
}
void CNewsStore::_FillStoreError(LPSTOREERROR pErrorInfo, IXPRESULT *pResult, LPSTR pszGroup)
{
TraceCall("CNewsStore::FillStoreError");
Assert(m_cRef >= 0); // Can be called during destruction
Assert(NULL != pErrorInfo);
if (pszGroup == NULL)
pszGroup = m_szGroup;
// Fill out the STOREERROR structure
ZeroMemory(pErrorInfo, sizeof(*pErrorInfo));
pErrorInfo->hrResult = pResult->hrResult;
pErrorInfo->uiServerError = pResult->uiServerError;
pErrorInfo->hrServerError = pResult->hrServerError;
pErrorInfo->dwSocketError = pResult->dwSocketError;
pErrorInfo->pszProblem = pResult->pszProblem;
pErrorInfo->pszDetails = pResult->pszResponse;
pErrorInfo->pszAccount = m_rInetServerInfo.szAccount;
pErrorInfo->pszServer = m_rInetServerInfo.szServerName;
pErrorInfo->pszFolder = pszGroup;
pErrorInfo->pszUserName = m_rInetServerInfo.szUserName;
pErrorInfo->pszProtocol = "NNTP";
pErrorInfo->pszConnectoid = m_rInetServerInfo.szConnectoid;
pErrorInfo->rasconntype = m_rInetServerInfo.rasconntype;
pErrorInfo->ixpType = IXP_NNTP;
pErrorInfo->dwPort = m_rInetServerInfo.dwPort;
pErrorInfo->fSSL = m_rInetServerInfo.fSSL;
pErrorInfo->fTrySicily = m_rInetServerInfo.fTrySicily;
pErrorInfo->dwFlags = 0;
}
//
// FUNCTION: CNewsStore::_CreateDataFilePath()
//
// PURPOSE: Creates a full path to a data file based on an account and a filename
//
// PARAMETERS:
// <in> pszAccount - account name
// <in> pszFileName - file name to be appended
// <out> pszPath - full path to data file
//
HRESULT CNewsStore::_CreateDataFilePath(LPCTSTR pszAccountId, LPCTSTR pszFileName, LPTSTR pszPath, DWORD cchPathSize)
{
HRESULT hr = NOERROR;
TCHAR szDirectory[MAX_PATH];
Assert(pszAccountId && *pszAccountId);
Assert(pszFileName);
Assert(pszPath);
// Get the Store Root Directory
hr = GetStoreRootDirectory(szDirectory, ARRAYSIZE(szDirectory));
// Validate that I have room
if (lstrlen(szDirectory) + lstrlen((LPSTR)pszFileName) + 2 >= MAX_PATH)
{
Assert(FALSE);
hr = TraceResult(E_FAIL);
goto exit;
}
if (SUCCEEDED(hr))
hr = OpenDirectory(szDirectory);
// Format the filename
wnsprintf(pszPath, cchPathSize,"%s\\%s.log", szDirectory, pszFileName);
exit:
return hr;
}
void AddRequestedRange(FOLDERINFO *pInfo, DWORD dwLow, DWORD dwHigh, BOOL *pfReq, BOOL *pfRead)
{
CRangeList *pRange;
Assert(pInfo != NULL);
Assert(dwLow <= dwHigh);
Assert(pfReq != NULL);
Assert(pfRead != NULL);
*pfReq = FALSE;
*pfRead = FALSE;
pRange = new CRangeList;
if (pRange != NULL)
{
if (pInfo->Requested.cbSize > 0)
pRange->Load(pInfo->Requested.pBlobData, pInfo->Requested.cbSize);
pRange->AddRange(dwLow, dwHigh);
*pfReq = pRange->Save(&pInfo->Requested.pBlobData, &pInfo->Requested.cbSize);
pRange->Release();
}
if (pInfo->Read.cbSize > 0)
{
pRange = new CRangeList;
if (pRange != NULL)
{
pRange->Load(pInfo->Read.pBlobData, pInfo->Read.cbSize);
pRange->DeleteRange(dwLow, dwHigh);
*pfRead = pRange->Save(&pInfo->Read.pBlobData, &pInfo->Read.cbSize);
pRange->Release();
}
}
}
HRESULT CNewsStore::MarkCrossposts(LPMESSAGEIDLIST pList, BOOL fRead)
{
PROPVARIANT var;
IMimeMessage *pMimeMsg;
IStream *pStream;
DWORD i;
MESSAGEINFO Message;
HRESULT hr;
LPSTR psz;
HROWSET hRowset = NULL;
if (NULL == pList)
{
hr = m_pFolder->CreateRowset(IINDEX_PRIMARY, NOFLAGS, &hRowset);
if (FAILED(hr))
return(hr);
}
for (i = 0; ; i++)
{
if (pList != NULL)
{
if (i >= pList->cMsgs)
break;
Message.idMessage = pList->prgidMsg[i];
hr = m_pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &Message, NULL);
if (FAILED(hr))
break;
else if (hr != DB_S_FOUND)
continue;
}
else
{
hr = m_pFolder->QueryRowset(hRowset, 1, (LPVOID *)&Message, NULL);
if (S_FALSE == hr)
{
hr = S_OK;
break;
}
else if (FAILED(hr))
{
break;
}
}
psz = NULL;
if (Message.pszXref == NULL &&
!!(Message.dwFlags & ARF_HASBODY))
{
if (SUCCEEDED(MimeOleCreateMessage(NULL, &pMimeMsg)))
{
if (SUCCEEDED(m_pFolder->OpenStream(ACCESS_READ, Message.faStream, &pStream)))
{
if (SUCCEEDED(hr = pMimeMsg->Load(pStream)))
{
var.vt = VT_EMPTY;
if (SUCCEEDED(pMimeMsg->GetProp(PIDTOSTR(STR_HDR_XREF), NOFLAGS, &var)))
{
Message.pszXref = var.pszVal;
psz = var.pszVal;
m_pFolder->UpdateRecord(&Message);
}
}
pStream->Release();
}
pMimeMsg->Release();
}
}
if (Message.pszXref != NULL && *Message.pszXref != 0)
_MarkCrossposts(Message.pszXref, fRead);
if (psz != NULL)
MemFree(psz);
m_pFolder->FreeRecord(&Message);
}
if (hRowset != NULL)
m_pFolder->CloseRowset(&hRowset);
return(hr);
}
void CNewsStore::_MarkCrossposts(LPCSTR szXRefs, BOOL fRead)
{
HRESULT hr;
CRangeList *pRange;
BOOL fReq, fFree;
DWORD dwArtNum;
IMessageFolder *pFolder;
MESSAGEINFO Message;
FOLDERINFO info;
LPSTR szT = StringDup(szXRefs);
LPSTR psz = szT, pszNum;
if (!szT)
return;
// skip over the server field
// $BUGBUG - we should really verify that our server generated the XRef
while (*psz && *psz != ' ')
psz++;
while (1)
{
// skip whitespace
while (*psz && (*psz == ' ' || *psz == '\t'))
psz++;
if (!*psz)
break;
// find the article num
pszNum = psz;
while (*pszNum && *pszNum != ':')
pszNum++;
if (!*pszNum)
break;
*pszNum++ = 0;
// Bug #47253 - Don't pass NULL pointers to SHLWAPI.
if (!*pszNum)
break;
dwArtNum = StrToInt(pszNum);
if (lstrcmpi(psz, m_szGroup) != 0)
{
ZeroMemory(&info, sizeof(FOLDERINFO));
info.idParent = m_idParent;
info.pszName = psz;
if (DB_S_FOUND == m_pStore->FindRecord(IINDEX_ALL, COLUMNS_ALL, &info, NULL))
{
if (!!(info.dwFlags & FOLDER_SUBSCRIBED))
{
fReq = FALSE;
if (info.Requested.cbSize > 0)
{
pRange = new CRangeList;
if (pRange != NULL)
{
pRange->Load(info.Requested.pBlobData, info.Requested.cbSize);
fReq = pRange->IsInRange(dwArtNum);
pRange->Release();
}
}
if (fReq)
{
hr = m_pStore->OpenFolder(info.idFolder, NULL, NOFLAGS, &pFolder);
if (SUCCEEDED(hr))
{
ZeroMemory(&Message, sizeof(MESSAGEINFO));
Message.idMessage = (MESSAGEID)((DWORD_PTR)dwArtNum);
hr = pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &Message, NULL);
if (DB_S_FOUND == hr)
{
if (fRead ^ !!(Message.dwFlags & ARF_READ))
{
Message.dwFlags ^= ARF_READ;
if (fRead && !!(Message.dwFlags & ARF_DOWNLOAD))
Message.dwFlags ^= ARF_DOWNLOAD;
pFolder->UpdateRecord(&Message);
}
pFolder->FreeRecord(&Message);
}
pFolder->Release();
}
}
else
{
pRange = new CRangeList;
if (pRange != NULL)
{
if (info.Read.cbSize > 0)
pRange->Load(info.Read.pBlobData, info.Read.cbSize);
if (fRead)
pRange->AddRange(dwArtNum);
else
pRange->DeleteRange(dwArtNum);
fFree = pRange->Save(&info.Read.pBlobData, &info.Read.cbSize);
pRange->Release();
m_pStore->UpdateRecord(&info);
if (fFree)
MemFree(info.Read.pBlobData);
}
}
}
m_pStore->FreeRecord(&info);
}
}
// skip over digits
while (IsDigit(pszNum))
pszNum++;
psz = pszNum;
}
MemFree(szT);
}
HRESULT CNewsStore::GetWatchedInfo(FOLDERID idFolder, IStoreCallback *pCallback)
{
HRESULT hr;
// Stack
TraceCall("CNewsStore::GetWatchedInfo");
AssertSingleThreaded;
Assert(pCallback != NULL);
Assert(m_op.tyOperation == SOT_INVALID);
m_op.tyOperation = SOT_GET_WATCH_INFO;
m_op.pfnState = c_rgpfnGetWatchInfo;
m_op.iState = 0;
m_op.cState = ARRAYSIZE(c_rgpfnGetWatchInfo);
m_op.pCallback = pCallback;
m_op.pCallback->AddRef();
m_op.idFolder = idFolder;
hr = _BeginDeferredOperation();
return hr;
}
HRESULT CNewsStore::XHdrReferences(void)
{
HRESULT hr = S_OK;
FOLDERINFO fi;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
if (SUCCEEDED(m_pStore->GetFolderInfo(m_op.idFolder, &fi)))
{
if (fi.dwClientWatchedHigh < fi.dwServerHigh)
{
m_dwWatchLow = max(fi.dwClientHigh + 1, fi.dwClientWatchedHigh + 1);
m_dwWatchHigh = fi.dwServerHigh;
// Save our new high value
fi.dwClientWatchedHigh = fi.dwServerHigh;
m_pStore->UpdateRecord(&fi);
// Check to see if we have any work to do
if (m_dwWatchLow <= m_dwWatchHigh)
{
// Allocate an array for the retreived data
if (!MemAlloc((LPVOID *) &m_rgpszWatchInfo, sizeof(LPTSTR) * (m_dwWatchHigh - m_dwWatchLow + 1)))
{
m_pStore->FreeRecord(&fi);
return (E_OUTOFMEMORY);
}
ZeroMemory(m_rgpszWatchInfo, sizeof(LPTSTR) * (m_dwWatchHigh - m_dwWatchLow + 1));
m_cRange.Clear();
m_cTotal = 0;
m_cCurrent = 0;
m_op.dwProgress = 0;
m_op.dwTotal = m_dwWatchHigh - m_dwWatchLow;
m_fXhdrSubject = FALSE;
RANGE range;
range.idType = RT_RANGE;
range.dwFirst = m_dwWatchLow;
range.dwLast = m_dwWatchHigh;
hr = m_pTransport->CommandXHDR("References", &range, NULL);
if (hr == S_OK)
{
m_op.nsPending = NS_XHDR;
hr = E_PENDING;
}
}
}
m_pStore->FreeRecord(&fi);
}
return(hr);
}
HRESULT CNewsStore::_HandleXHdrReferencesResponse(LPNNTPRESPONSE pResp)
{
NNTPXHDR *pHdr;
// Check for error
if (FAILED(pResp->rIxpResult.hrResult))
{
Assert(pResp->fDone);
_FillStoreError(&m_op.error, &pResp->rIxpResult);
m_op.pCallback->OnComplete(m_op.tyOperation, pResp->rIxpResult.hrResult, NULL, &m_op.error);
return(S_OK);
}
// Loop through the returned data and insert those values into our array
for (DWORD i = 0; i < pResp->rXhdr.cHeaders; i++)
{
pHdr = &(pResp->rXhdr.rgHeaders[i]);
Assert(pHdr->dwArticleNum <= m_dwWatchHigh);
// Some servers return "(none)" for articles that don't have that
// header. Smart servers just don't return anything.
if (0 != lstrcmpi(pHdr->pszHeader, "(none)"))
{
m_rgpszWatchInfo[pHdr->dwArticleNum - m_dwWatchLow] = PszDupA(pHdr->pszHeader);
}
}
// Show a little progress here. This is actually a little complicated. The
// data returned might have a single line for each header, or might be sparse.
// we need to show progress proportional to how far we are through the headers.
m_op.dwProgress = (pResp->rXhdr.rgHeaders[pResp->rXhdr.cHeaders - 1].dwArticleNum - m_dwWatchLow);
m_op.pCallback->OnProgress(SOT_GET_WATCH_INFO, m_op.dwProgress, m_op.dwTotal,
m_rInetServerInfo.szServerName);
return (S_OK);
}
HRESULT CNewsStore::XHdrSubject(void)
{
HRESULT hr = S_OK;
FOLDERINFO fi;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
// Check to see if we have any work to do
if ((m_dwWatchLow > m_dwWatchHigh) || (m_dwWatchLow == 0 && m_dwWatchHigh == 0))
return (S_OK);
m_op.dwProgress = 0;
m_op.dwTotal = m_dwWatchHigh - m_dwWatchLow;
RANGE range;
range.idType = RT_RANGE;
range.dwFirst = m_dwWatchLow;
range.dwLast = m_dwWatchHigh;
m_fXhdrSubject = TRUE;
hr = m_pTransport->CommandXHDR("Subject", &range, NULL);
if (hr == S_OK)
{
m_op.nsPending = NS_XHDR;
hr = E_PENDING;
}
return(hr);
}
HRESULT CNewsStore::_HandleXHdrSubjectResponse(LPNNTPRESPONSE pResp)
{
NNTPXHDR *pHdr;
// Check for error
if (FAILED(pResp->rIxpResult.hrResult))
{
Assert(pResp->fDone);
_FillStoreError(&m_op.error, &pResp->rIxpResult);
m_op.pCallback->OnComplete(m_op.tyOperation, pResp->rIxpResult.hrResult, NULL, &m_op.error);
return(S_OK);
}
// Loop through the returned data see which ones are watched
for (DWORD i = 0; i < pResp->rXhdr.cHeaders; i++)
{
pHdr = &(pResp->rXhdr.rgHeaders[i]);
Assert(pHdr->dwArticleNum <= m_dwWatchHigh);
// Check to see if this is part of a watched thread
if (_IsWatchedThread(m_rgpszWatchInfo[pHdr->dwArticleNum - m_dwWatchLow], pHdr->pszHeader))
{
m_cRange.AddRange(pHdr->dwArticleNum);
m_cTotal++;
}
}
// Show a little progress here.
m_op.dwProgress += pResp->rXhdr.cHeaders;
m_op.pCallback->OnProgress(SOT_GET_WATCH_INFO, m_op.dwProgress, m_op.dwTotal,
m_rInetServerInfo.szServerName);
// If this is the end of the xhdr data, we can free our array of references
if (pResp->fDone)
{
for (UINT i = 0; i < (m_dwWatchHigh - m_dwWatchLow + 1); i++)
{
if (m_rgpszWatchInfo[i])
MemFree(m_rgpszWatchInfo[i]);
}
MemFree(m_rgpszWatchInfo);
m_rgpszWatchInfo = 0;
m_dwWatchLow = 0;
m_dwWatchHigh = 0;
}
return (S_OK);
}
BOOL CNewsStore::_IsWatchedThread(LPSTR pszRef, LPSTR pszSubject)
{
// Get the Parent
Assert(m_pFolder);
return(S_OK == m_pFolder->IsWatched(pszRef, pszSubject) ? TRUE : FALSE);
}
HRESULT CNewsStore::WatchedArticles(void)
{
HRESULT hr = S_OK;
ARTICLEID rArticleId;
AssertSingleThreaded;
Assert(m_pTransport != NULL);
// Check to see if we have any work to do
if (m_cRange.Cardinality() == 0)
return (S_OK);
m_op.pCallback->OnProgress(SOT_GET_WATCH_INFO, ++m_cCurrent, m_cTotal, NULL);
m_op.idServerMessage = m_cRange.Min();
m_op.idMessage = 0;
m_cRange.DeleteRange(m_cRange.Min());
// Create a stream
if (FAILED(hr = CreatePersistentWriteStream(m_pFolder, &m_op.pStream, &m_op.faStream)))
return (E_OUTOFMEMORY);
hr = Article();
if (hr == E_PENDING)
m_op.iState--;
return (hr);
}
HRESULT CNewsStore::_SaveMessageToStore(IMessageFolder *pFolder, DWORD id, LPSTREAM pstm)
{
FOLDERINFO info;
BOOL fFreeReq, fFreeRead;
IMimeMessage *pMsg = 0;
HRESULT hr;
MESSAGEID idMessage = (MESSAGEID)((DWORD_PTR)id);
// Create a new message
if (SUCCEEDED(hr = MimeOleCreateMessage(NULL, &pMsg)))
{
if (SUCCEEDED(hr = pMsg->Load(pstm)))
{
if (SUCCEEDED(m_pStore->GetFolderInfo(m_op.idFolder, &info)))
{
fFreeReq = FALSE;
fFreeRead = FALSE;
AddRequestedRange(&info, id, id, &fFreeReq, &fFreeRead);
info.dwNotDownloaded = NewsUtil_GetNotDownloadCount(&info);
m_pStore->UpdateRecord(&info);
if (fFreeReq)
MemFree(info.Requested.pBlobData);
if (fFreeRead)
MemFree(info.Read.pBlobData);
}
hr = m_pFolder->SaveMessage(&idMessage, 0, 0, m_op.pStream, pMsg, NOSTORECALLBACK);
}
pMsg->Release();
}
return (hr);
}