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
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);
|
|
}
|