/* * 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 #include #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: // pszAccount - account name // pszFileName - file name to be appended // 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); }