windows-nt/Source/XPSP1/NT/shell/ext/webcheck/throttle.cpp
2020-09-26 16:20:57 +08:00

1874 lines
49 KiB
C++

#include "private.h"
#include "throttle.h"
#include "subsmgrp.h"
#include <mluisupp.h>
#define TF_THISMODULE TF_THROTTLER
const int MAX_AUTOCACHESIZE_ASK = 2;
const int MIN_CACHE_INCREASE = 1024; // in KB
// Strings for cache restrictions
const TCHAR c_szKeyRestrict[] = TEXT("Software\\Policies\\Microsoft\\Internet Explorer\\Control Panel");
const TCHAR c_szCache[] = TEXT("Cache");
CThrottler *CThrottler::s_pThrottler = NULL;
const CFactoryData CThrottler::s_ThrottlerFactoryData =
{
&CLSID_SubscriptionThrottler, CreateInstance, 0
};
#ifdef DEBUG
void DUMPITEM(CHAR *pszMsg, const SUBSCRIPTIONCOOKIE *pCookie)
{
ISubscriptionItem *psi;
if (SUCCEEDED(SubscriptionItemFromCookie(FALSE, pCookie, &psi)))
{
BSTR bstrName;
if (SUCCEEDED(ReadBSTR(psi, c_szPropName, &bstrName)))
{
TraceMsgA(TF_THISMODULE, "%s: %S", pszMsg, bstrName);
SysFreeString(bstrName);
}
psi->Release();
}
}
#else
#define DUMPITEM(pszMsg, pCookie)
#endif
// dwSyncFlags has 8 bits of enum (EVENTMASK) and the rest is flags
inline BOOL IsSyncEvent(DWORD dwSyncFlags, DWORD dwSyncEvent)
{
return (dwSyncFlags & SYNCMGRFLAG_EVENTMASK) == dwSyncEvent;
}
inline BOOL IsSyncEventFlag(DWORD dwSyncFlags, DWORD dwSyncEvent)
{
return (dwSyncFlags & dwSyncEvent) != 0;
}
inline BOOL IsIgnoreIdleSyncEvent(DWORD dwSyncFlags)
{
return !IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_IDLE);
/*
return IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_CONNECT) ||
IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_PENDINGDISCONNECT) ||
IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_MANUAL) ||
IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_INVOKE);
*/
}
inline BOOL IsScheduleSyncEvent(DWORD dwSyncFlags)
{
return IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_SCHEDULED) ||
IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_IDLE);
}
class CThrottlerProxy : public ISubscriptionThrottler
{
public:
CThrottlerProxy(CThrottler *pThrottler)
{
m_cRef = 1;
m_pThrottler = pThrottler;
m_pThrottler->ExternalAddRef();
}
STDMETHODIMP QueryInterface(REFIID riid, void **punk)
{
if ((riid == IID_IUnknown) || (riid == IID_ISubscriptionThrottler))
{
*punk = (ISubscriptionThrottler *)this;
AddRef();
return S_OK;
}
else
{
*punk = NULL;
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG) AddRef()
{
return ++m_cRef;
}
STDMETHODIMP_(ULONG) Release()
{
if (--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
STDMETHODIMP GetSubscriptionRunState(
/* [in] */ DWORD dwNumCookies,
/* [size_is][in] */ const SUBSCRIPTIONCOOKIE *pCookies,
/* [size_is][out] */ DWORD *pdwRunState)
{
return m_pThrottler->GetSubscriptionRunState(dwNumCookies, pCookies, pdwRunState);
}
STDMETHODIMP AbortItems(
/* [in] */ DWORD dwNumCookies,
/* [size_is][in] */ const SUBSCRIPTIONCOOKIE *pCookies)
{
return m_pThrottler->AbortItems(dwNumCookies, pCookies);
}
STDMETHODIMP AbortAll()
{
return m_pThrottler->AbortAll();
}
private:
ULONG m_cRef;
CThrottler *m_pThrottler;
~CThrottlerProxy()
{
m_pThrottler->ExternalRelease();
}
};
HRESULT CThrottler::CreateInstance(IUnknown *punkOuter, IUnknown **ppunk)
{
HRESULT hr;
ASSERT(NULL == punkOuter);
ASSERT(NULL != ppunk);
if (NULL != CThrottler::s_pThrottler)
{
*ppunk = new CThrottlerProxy(CThrottler::s_pThrottler);
if (NULL != *ppunk)
{
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
}
else
{
TraceMsg(TF_ALWAYS, "UNEXPECTED ERROR: Failed to attached to throttler in CreateInstance");
hr = E_UNEXPECTED;
}
return hr;
}
CThrottler::CThrottler()
{
ASSERT(NULL == s_pThrottler);
ASSERT(NULL == m_pItemsHead);
ASSERT(NULL == m_pItemsTail);
ASSERT(NULL == m_updateQueue[0]);
// APPCOMPAT - this is only until msidle is multi-client aware.
IdleEnd();
//m_fUserIsIdle = TRUE; // TODO: need to determine this better
IdleBegin(NULL);
m_cRef = 1;
}
CThrottler::~CThrottler()
{
DBG("Destroying Throttler");
ASSERT(GetCurrentThreadId() == m_dwThreadId);
IdleEnd();
// Destroy window
if (m_hwndThrottler)
{
SetWindowLongPtr(m_hwndThrottler, GWLP_USERDATA, 0);
DestroyWindow(m_hwndThrottler);
m_hwndThrottler = NULL;
}
s_pThrottler = NULL;
RevokeClassObject();
}
HRESULT CThrottler::RevokeClassObject()
{
HRESULT hr;
if (m_dwRegister)
{
hr = CoRevokeClassObject(m_dwRegister);
m_dwRegister = 0;
}
else
{
hr = S_FALSE;
}
return hr;
}
HRESULT /* static */ CThrottler::GetThrottler(CThrottler **ppThrottler)
{
HRESULT hr = S_OK;
ASSERT(NULL != ppThrottler);
if (NULL != ppThrottler)
{
*ppThrottler = NULL;
// If there is no throttler create a new one
if (NULL == s_pThrottler)
{
DBG("Creating new throttler in GetThrottler");
s_pThrottler = new CThrottler;
if (NULL != s_pThrottler)
{
IClassFactory *pcf = new CClassFactory(&s_ThrottlerFactoryData);
if (NULL != pcf)
{
HRESULT hrRegister = CoRegisterClassObject(CLSID_SubscriptionThrottler,
pcf,
CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE,
&s_pThrottler->m_dwRegister);
if (FAILED(hrRegister))
{
TraceMsg(TF_ALWAYS, "CoRegisterClassObject failed - other processes can't talk to us!");
}
pcf->Release();
}
*ppThrottler = s_pThrottler;
#ifdef DEBUG
s_pThrottler->m_dwThreadId = GetCurrentThreadId();
#endif
}
else
{
DBG("Failed to create Throttler class factory");
hr = E_OUTOFMEMORY;
}
}
else
{
// Attach to existing throttler
ASSERT(GetCurrentThreadId() == s_pThrottler->m_dwThreadId);
s_pThrottler->AddRef();
*ppThrottler = s_pThrottler;
}
}
else
{
hr = E_INVALIDARG;
}
return hr;
}
void CThrottler::OnIdleStateChange(DWORD dwState)
{
if (NULL != s_pThrottler)
{
switch(dwState)
{
case STATE_USER_IDLE_BEGIN:
DBG("OnIdleStateChange: Idle Begin");
#ifdef DEBUG
LogEvent(TEXT("Idle state begins"));
#endif
s_pThrottler->OnIdleBegin();
break;
case STATE_USER_IDLE_END:
DBG("OnIdleStateChange: Idle End");
#ifdef DEBUG
LogEvent(TEXT("Idle state ends"));
#endif
s_pThrottler->OnIdleEnd();
break;
}
}
}
void CThrottler::OnIdleBegin()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
m_fUserIsIdle = TRUE;
FillTheQueue();
}
void CThrottler::OnIdleEnd()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
m_fUserIsIdle = FALSE;
for (int i = 0; i < ARRAYSIZE(m_updateQueue); i++)
{
if ((NULL != m_updateQueue[i]) &&
(m_updateQueue[i]->m_dwRunState & RS_SUSPENDONIDLE))
{
DUMPITEM("Suspending in CThrottler::OnIdleEnd", &m_updateQueue[i]->m_cookie);
ISubscriptionAgentControl *pSubsAgentCtl;
CUpdateItem *pUpdateItem = m_updateQueue[i];
pSubsAgentCtl = m_updateQueue[i]->m_pSubsAgentCtl;
m_updateQueue[i]->m_dwRunState &= ~RS_UPDATING;
m_updateQueue[i]->m_dwRunState |= RS_SUSPENDED;
m_updateQueue[i] = NULL;
ASSERT(NULL != pSubsAgentCtl);
if (SUCCEEDED(pSubsAgentCtl->PauseUpdate(0)))
{
WCHAR wszMsg[256];
MLLoadStringW(IDS_UPDATE_PAUSED, wszMsg, ARRAYSIZE(wszMsg));
NotifyHandlers(NH_UPDATEPROGRESS, &pUpdateItem->m_cookie, -1,
-1, -1, WC_INTERNAL_S_PAUSED, wszMsg);
}
}
}
FillTheQueue();
}
STDMETHODIMP CThrottler::QueryInterface(REFIID riid, void **ppv)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
if (NULL == ppv)
{
return E_INVALIDARG;
}
if ((IID_IUnknown == riid) || (IID_ISubscriptionAgentEvents == riid))
{
*ppv = (ISubscriptionAgentEvents *)this;
}
else if (IID_ISubscriptionThrottler == riid)
{
*ppv = (ISubscriptionThrottler *)this;
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
ULONG CThrottler::ExternalAddRef()
{
AddRef();
return ++m_cExternalRef;
}
ULONG CThrottler::ExternalRelease()
{
ULONG cRef = --m_cExternalRef;
Release();
return cRef;
}
STDMETHODIMP_(ULONG) CThrottler::AddRef()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
return ++m_cRef;
}
STDMETHODIMP_(ULONG) CThrottler::Release()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
if (--m_cRef == 0)
{
delete this;
return 0;
}
return m_cRef;
}
HRESULT CThrottler::NotifyHandlers(int idCmd, const SUBSCRIPTIONCOOKIE *pSubscriptionCookie, ...)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr = S_OK;
va_list va;
long lSizeDownloaded = -1;
long lProgressCurrent = -1;
long lProgressMax = -1;
HRESULT hrParam = E_UNEXPECTED;
LPCWSTR wszParam = NULL;
va_start(va, pSubscriptionCookie);
// First extract args
switch (idCmd)
{
case NH_UPDATEBEGIN:
// Nothing to do for now
break;
case NH_UPDATEPROGRESS:
lSizeDownloaded = va_arg(va, long);
lProgressCurrent = va_arg(va, long);
lProgressMax = va_arg(va, long);
hrParam = va_arg(va, HRESULT);
wszParam = va_arg(va, LPCWSTR);
break;
case NH_UPDATEEND:
lSizeDownloaded = va_arg(va, long);
hrParam = va_arg(va, HRESULT);
wszParam = va_arg(va, LPCWSTR);
break;
case NH_REPORTERROR:
hrParam = va_arg(va, HRESULT);
wszParam = va_arg(va, LPCWSTR);
break;
default:
ASSERT(0); // Don't know what to do
hr = E_UNEXPECTED;
break;
}
// Now loop
HRESULT hrTemp = S_OK;
CSyncMgrNode *pSyncMgrNode = m_pSyncMgrs;
while (pSyncMgrNode)
{
COfflineSync *pOfflineSync = pSyncMgrNode->m_pOfflineSync;
pSyncMgrNode = pSyncMgrNode->m_pNext;
switch (idCmd)
{
case NH_UPDATEBEGIN:
hrTemp = pOfflineSync->UpdateBegin(pSubscriptionCookie);
break;
case NH_UPDATEPROGRESS:
hrTemp = pOfflineSync->UpdateProgress(pSubscriptionCookie,
lSizeDownloaded,
lProgressCurrent,
lProgressMax,
hrParam,
wszParam);
break;
case NH_UPDATEEND:
hrTemp = pOfflineSync->UpdateEnd(pSubscriptionCookie,
lSizeDownloaded,
hrParam,
wszParam);
break;
case NH_REPORTERROR:
hrTemp = pOfflineSync->ReportError(pSubscriptionCookie,
hrParam,
wszParam);
break;
}
if (FAILED(hrTemp))
{
hr = hrTemp;
}
}
va_end(va);
return hr;
}
// ISubscriptionAgentEvents members
STDMETHODIMP CThrottler::UpdateBegin(const SUBSCRIPTIONCOOKIE *pSubscriptionCookie)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
CUpdateItem *pUpdateItem;
hr = FindCookie(pSubscriptionCookie, &pUpdateItem);
ASSERT(SUCCEEDED(hr));
if (SUCCEEDED(hr))
{
DUMPITEM("CThrottler::UpdateBegin", pSubscriptionCookie);
pUpdateItem->m_dwRunState &= ~(RS_READY | RS_SUSPENDED);
pUpdateItem->m_dwRunState |= RS_UPDATING;
hr = NotifyHandlers(NH_UPDATEBEGIN, pSubscriptionCookie);
}
return hr;
}
STDMETHODIMP CThrottler::UpdateProgress(
const SUBSCRIPTIONCOOKIE *pSubscriptionCookie,
long lSizeDownloaded,
long lProgressCurrent,
long lProgressMax,
HRESULT hrStatus,
LPCWSTR wszStatus)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
CUpdateItem *pUpdateItem;
// TODO:
// Adjust the max to fool syncmgr
if (SUCCEEDED(FindCookie(pSubscriptionCookie, &pUpdateItem)))
{
if ((lProgressMax < 0) || (lProgressMax <= lProgressCurrent))
{
if (pUpdateItem->m_nMax <= lProgressCurrent)
{
pUpdateItem->m_nMax = (lProgressCurrent * 3) / 2;
}
lProgressMax = pUpdateItem->m_nMax;
}
}
hr = NotifyHandlers(NH_UPDATEPROGRESS, pSubscriptionCookie, lSizeDownloaded,
lProgressCurrent, lProgressMax, hrStatus, wszStatus);
return hr;
}
STDMETHODIMP CThrottler::UpdateEnd(
const SUBSCRIPTIONCOOKIE *pSubscriptionCookie,
long lSizeDownloaded,
HRESULT hrResult,
LPCWSTR wszResult)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
CUpdateItem *pUpdateItem;
SUBSCRIPTIONCOOKIE cookie = *pSubscriptionCookie;
hr = FindCookie(pSubscriptionCookie, &pUpdateItem);
if (SUCCEEDED(hr))
{
DUMPITEM("CThrottler::UpdateEnd", pSubscriptionCookie);
pUpdateItem->m_dwRunState &= ~(RS_READY | RS_SUSPENDED | RS_UPDATING | RS_SUSPENDONIDLE);
pUpdateItem->m_dwRunState |= RS_COMPLETED;
RemoveItemFromList(pUpdateItem, TRUE);
// ************************************************************************
// Don't use anything that could have come from pUpdateItem after this
// including the pSubscriptionCookie above which came from an agent which
// probably no longer exists!
// (actually, the agent keeps itself alive until this call returns)
// ************************************************************************
}
hr = NotifyHandlers(NH_UPDATEEND, &cookie,
lSizeDownloaded, hrResult, wszResult);
FireSubscriptionEvent(SUBSNOTF_SYNC_STOP, &cookie);
FillTheQueue();
return hr;
}
STDMETHODIMP CThrottler::ReportError(
const SUBSCRIPTIONCOOKIE *pSubscriptionCookie,
HRESULT hrError,
LPCWSTR wszError)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
if (INET_E_AGENT_EXCEEDING_CACHE_SIZE == hrError)
{
// Agent is notifying us that they're about to exceed the cache size.
hr = AutoCacheSizeRequest(pSubscriptionCookie);
}
else
hr = NotifyHandlers(NH_REPORTERROR, pSubscriptionCookie, hrError, wszError);
return hr;
}
STDMETHODIMP CThrottler::GetSubscriptionRunState(
/* [in] */ DWORD dwNumCookies,
/* [size_is][in] */ const SUBSCRIPTIONCOOKIE *pCookies,
/* [size_is][out] */ DWORD *pdwRunState)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
if ((0 == dwNumCookies) ||
(NULL == pCookies) ||
(NULL == pdwRunState))
{
return E_INVALIDARG;
}
for (DWORD i = 0; i < dwNumCookies; i++, pCookies++, pdwRunState++)
{
CUpdateItem *pUpdateItem;
if (SUCCEEDED(FindCookie(pCookies, &pUpdateItem)))
{
*pdwRunState = pUpdateItem->m_dwRunState;
}
else
{
*pdwRunState = 0;
}
}
return S_OK;
}
// DoAbortItem will cause the CThrottler to get released if the last running
// agent is aborted (Agent notifies it's done, SyncMgr releases throttler,
// then agent releases throttler)
HRESULT CThrottler::DoAbortItem(CUpdateItem *pUpdateItem)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
ASSERT(((pUpdateItem->m_dwRunState & (RS_UPDATING | RS_SUSPENDED)) &&
(NULL != pUpdateItem->m_pSubsAgentCtl))
||
(NULL == pUpdateItem->m_pSubsAgentCtl));
if ((pUpdateItem->m_dwRunState & (RS_UPDATING | RS_SUSPENDED)) &&
(NULL != pUpdateItem->m_pSubsAgentCtl))
{
DUMPITEM("CThrottler::DoAbortItem with existing Agent", &pUpdateItem->m_cookie);
hr = pUpdateItem->m_pSubsAgentCtl->AbortUpdate(0);
}
else
{
WCHAR wszMsg[256];
MLLoadStringW(IDS_STATUS_ABORTED, wszMsg, ARRAYSIZE(wszMsg));
DUMPITEM("CThrottler::DoAbortItem with no Agent", &pUpdateItem->m_cookie);
ReportThrottlerError(&pUpdateItem->m_cookie, E_ABORT, wszMsg);
hr = UpdateEnd(&pUpdateItem->m_cookie, 0, E_ABORT, wszMsg);
}
return hr;
}
STDMETHODIMP CThrottler::AbortItems(
/* [in] */ DWORD dwNumCookies,
/* [size_is][in] */ const SUBSCRIPTIONCOOKIE *pCookies)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
if ((0 == dwNumCookies) ||
(NULL == pCookies))
{
return E_INVALIDARG;
}
if (FAILED(CreateThrottlerWnd()))
return E_FAIL;
hr = S_OK;
void *pItem = MemAlloc(LMEM_FIXED, dwNumCookies * sizeof(SUBSCRIPTIONCOOKIE));
if (pItem)
{
#ifdef DEBUG
for (DWORD i = 0; i < dwNumCookies; i++)
{
DUMPITEM("Aborting in CThrottler::AbortItems", &pCookies[i]);
}
#endif
memcpy(pItem, pCookies, dwNumCookies * sizeof(SUBSCRIPTIONCOOKIE));
PostMessage(m_hwndThrottler, WM_THROTTLER_ABORTITEM, (WPARAM)dwNumCookies, (LPARAM)pItem);
}
else
{
DBG_WARN("Memory alloc failed in CThrottler::AbortItems");
hr = S_FALSE;
}
return hr;
}
STDMETHODIMP CThrottler::ActuallyAbortItems(
/* [in] */ DWORD dwNumCookies,
/* [size_is][in] */ const SUBSCRIPTIONCOOKIE *pCookies)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
if ((0 == dwNumCookies) ||
(NULL == pCookies))
{
return E_INVALIDARG;
}
hr = S_OK;
// DoAbortItem will cause the CThrottler to get released if the last
// running agent is aborted. Protect against that.
AddRef();
for (DWORD i = 0; i < dwNumCookies; i++, pCookies++)
{
HRESULT hrItem;
CUpdateItem *pUpdateItem;
hrItem = FindCookie(pCookies, &pUpdateItem);
if (SUCCEEDED(hrItem))
{
hrItem = DoAbortItem(pUpdateItem);
// ************************************************************************
// pUpdateItem is no longer valid!
// ************************************************************************
}
if (FAILED(hrItem))
{
hr = S_FALSE;
}
}
Release();
return hr;
}
HRESULT CThrottler::CreateThrottlerWnd()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
if (!m_hwndThrottler)
{
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = CThrottler::ThrottlerWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = g_hInst;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = (HBRUSH)NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = THROTTLER_WNDCLASS;
RegisterClass(&wc);
m_hwndThrottler = CreateWindow(THROTTLER_WNDCLASS, TEXT("YO"), WS_OVERLAPPED,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, g_hInst, (LPVOID)this);
if (NULL == m_hwndThrottler)
{
DBG_WARN("CThrottler CreateWindow failed");
return E_FAIL;
}
}
return S_OK;
}
LRESULT CThrottler::ThrottlerWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
CThrottler *pThis = (CThrottler*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
// Validate pThis
#ifdef DEBUG
if (pThis && IsBadWritePtr(pThis, sizeof(*pThis)))
{
TraceMsg(TF_THISMODULE|TF_WARNING,
"Invalid 'this' in ThrottlerWndProc (0x%08x) - already destroyed?", pThis);
}
if (pThis)
{
ASSERT(GetCurrentThreadId() == pThis->m_dwThreadId);
ASSERT(GetCurrentThreadId() == GetWindowThreadProcessId(hWnd, NULL));
}
#endif
switch (Msg)
{
case WM_CREATE :
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
if (!pcs || !(pcs->lpCreateParams))
{
DBG_WARN("Invalid param ThrottlerWndProc Create");
return -1;
}
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) pcs->lpCreateParams);
return 0;
}
case WM_THROTTLER_ABORTALL:
if (pThis)
pThis->ActuallyAbortAll();
break;
case WM_THROTTLER_ABORTITEM:
if (pThis)
pThis->ActuallyAbortItems((ULONG) wParam, (SUBSCRIPTIONCOOKIE*) lParam);
MemFree((HLOCAL)lParam);
break;
case WM_THROTTLER_AUTOCACHESIZE_ASK:
if (pThis)
pThis->AutoCacheSizeAskUser((DWORD)lParam);
break;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return 0;
}
STDMETHODIMP CThrottler::AbortAll()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
if (FAILED(CreateThrottlerWnd()))
return E_FAIL;
DBG("Aborting all items");
PostMessage(m_hwndThrottler, WM_THROTTLER_ABORTALL, 0, 0);
return S_OK;
}
STDMETHODIMP CThrottler::ActuallyAbortAll()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr = S_OK;
CUpdateItem *pItem = m_pItemsHead;
if (FALSE == m_fAbortingAll)
{
m_fAbortingAll = TRUE;
while (pItem)
{
CUpdateItem *pUpdateItem = pItem;
// Move forward now since this item should get yanked!
pItem = pItem->m_pNext;
if (FAILED(DoAbortItem(pUpdateItem)))
{
hr = S_FALSE;
}
}
m_fAbortingAll = FALSE;
}
else
{
hr = S_FALSE;
}
return hr;
}
HRESULT CThrottler::Advise(COfflineSync *pOfflineSync)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
CSyncMgrNode *pSyncMgrNode;
ASSERT(NULL != pOfflineSync);
#ifdef DEBUG
pSyncMgrNode = m_pSyncMgrs;
while (pSyncMgrNode)
{
if (pSyncMgrNode->m_pOfflineSync == pOfflineSync)
{
ASSERT(0); // Shouldn't advise more than once!
}
pSyncMgrNode = pSyncMgrNode->m_pNext;
}
#endif
ASSERT(!m_hwndParent || (m_hwndParent == pOfflineSync->GetParentWindow()));
m_hwndParent = pOfflineSync->GetParentWindow();
pSyncMgrNode = new CSyncMgrNode(pOfflineSync, m_pSyncMgrs);
if (NULL != pSyncMgrNode)
{
pOfflineSync->AddRef();
m_pSyncMgrs = pSyncMgrNode;
hr = S_OK;
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
HRESULT CThrottler::Unadvise(COfflineSync *pOfflineSync)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr = E_FAIL;
CSyncMgrNode *pSyncMgrNode;
CSyncMgrNode **ppSyncMgrPrev;
ASSERT(NULL != pOfflineSync);
pSyncMgrNode = m_pSyncMgrs;
ppSyncMgrPrev = &m_pSyncMgrs;
while (pSyncMgrNode)
{
if (pSyncMgrNode->m_pOfflineSync == pOfflineSync)
{
*ppSyncMgrPrev = pSyncMgrNode->m_pNext;
delete pSyncMgrNode;
hr = S_OK;
break;
}
ppSyncMgrPrev = &pSyncMgrNode->m_pNext;
pSyncMgrNode = pSyncMgrNode->m_pNext;
}
ASSERT(SUCCEEDED(hr)); // This is internal goo so should not fail!
if (NULL == m_pSyncMgrs)
{
// Everyone has lost interest in us...
RevokeClassObject();
s_pThrottler = NULL;
while (m_cExternalRef > 0)
{
TraceMsg(TF_WARNING, "CThrottle::Unadvise m_cExternalRef = %d", m_cExternalRef);
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, TRUE))
{
DispatchMessage(&msg);
}
}
}
return hr;
}
int CThrottler::GetCookieIndexInQueue(const SUBSCRIPTIONCOOKIE *pCookie)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
int index = -1;
for (int i = 0; i < ARRAYSIZE(m_updateQueue); i++)
{
if ((NULL != m_updateQueue[i]) && (m_updateQueue[i]->m_cookie == *pCookie))
{
index = i;
break;
}
}
return index;
}
void CThrottler::FailedUpdate(HRESULT hr, const SUBSCRIPTIONCOOKIE *pCookie)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
WCHAR wszMsg[256];
int resID;
switch (hr)
{
case INET_E_SCHEDULED_UPDATES_DISABLED:
resID = IDS_SCHEDULED_UPDATES_DISABLED;
break;
case INET_E_SCHEDULED_UPDATES_RESTRICTED:
resID = IDS_SCHEDULED_UPDATES_RESTRICTED;
break;
case INET_E_SCHEDULED_UPDATE_INTERVAL:
resID = IDS_SCHEDULED_UPDATE_INTERVAL;
break;
case INET_E_SCHEDULED_EXCLUDE_RANGE:
resID = IDS_SCHEDULED_EXCLUDE_RANGE;
break;
default:
resID = IDS_CRAWL_STATUS_NOT_OK;
break;
}
MLLoadStringW(resID, wszMsg, ARRAYSIZE(wszMsg));
ReportThrottlerError(pCookie, hr, wszMsg);
UpdateEnd(pCookie, 0, hr, wszMsg);
}
void CThrottler::RunItem(int queueSlot, CUpdateItem *pUpdateItem)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr;
ASSERT(NULL == m_updateQueue[queueSlot]);
m_updateQueue[queueSlot] = pUpdateItem;
if (pUpdateItem->m_dwRunState & RS_SUSPENDED)
{
DUMPITEM("Resuming suspended item in CThrottler::RunItem", &pUpdateItem->m_cookie);
ASSERT(NULL != pUpdateItem->m_pSubsAgentCtl);
pUpdateItem->m_dwRunState |= RS_UPDATING;
pUpdateItem->m_dwRunState &= ~RS_SUSPENDED;
hr = pUpdateItem->m_pSubsAgentCtl->ResumeUpdate(0);
if (SUCCEEDED(hr))
{
WCHAR wszMsg[256];
MLLoadStringW(IDS_UPDATE_RESUMING, wszMsg, ARRAYSIZE(wszMsg));
NotifyHandlers(NH_UPDATEPROGRESS, &pUpdateItem->m_cookie, -1,
-1, -1, WC_INTERNAL_S_RESUMING, wszMsg);
}
}
else
{
ISubscriptionItem *psi;
hr = SubscriptionItemFromCookie(FALSE, &pUpdateItem->m_cookie, &psi);
if (SUCCEEDED(hr))
{
SUBSCRIPTIONITEMINFO sii;
sii.cbSize = sizeof(SUBSCRIPTIONITEMINFO);
hr = psi->GetSubscriptionItemInfo(&sii);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(sii.clsidAgent,
NULL,
CLSCTX_INPROC_SERVER,
IID_ISubscriptionAgentControl,
(void**)&pUpdateItem->m_pSubsAgentCtl);
if (SUCCEEDED(hr))
{
DUMPITEM("Running item in CThrottler::RunItem", &pUpdateItem->m_cookie);
hr = pUpdateItem->m_pSubsAgentCtl->StartUpdate(psi,
(ISubscriptionAgentEvents *)this);
FireSubscriptionEvent(SUBSNOTF_SYNC_START, &pUpdateItem->m_cookie);
}
else
{
DBG_WARN("CoCreate Agent FAILED in CThrottler::RunItem");
}
}
psi->Release();
}
else
{
DBG_WARN("SubscriptionItemFromCookie FAILED in CThrottler::RunItem");
}
}
if (FAILED(hr))
{
FailedUpdate(hr, &pUpdateItem->m_cookie);
}
}
int CThrottler::GetFreeQueueSlot()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
int index = -1;
for (int i = 0; i < ARRAYSIZE(m_updateQueue); i++)
{
if (NULL == m_updateQueue[i])
{
index = i;
break;
}
}
return index;
}
void CThrottler::FillTheQueue()
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
if ((FALSE == m_fFillingTheQueue) && // avoid re-entrancy
(FALSE == m_fAbortingAll) && // avoid re-entrancy
(FALSE == m_fAutoCacheSizePending)) // we have a dialog up for the user
{
m_fFillingTheQueue = TRUE;
CUpdateItem *pNextItem = m_pItemsHead;
CUpdateItem *pItem;
while (NULL != pNextItem)
{
pItem = pNextItem;
// Move ahead since this item may not be here
// if we run it and it fails
pNextItem = pNextItem->m_pNext;
if (!(pItem->m_dwRunState & (RS_COMPLETED | RS_UPDATING)))
{
int freeSlot = GetFreeQueueSlot();
if ((freeSlot >= 0) &&
(m_fUserIsIdle || (!(pItem->m_dwRunState & RS_SUSPENDONIDLE))))
{
RunItem(freeSlot, pItem);
}
else
{
// If we didn't run it then let's make sure the UI reflects the current
// state properly
HRESULT hrStatus;
WCHAR wszMsg[256];
if ((pItem->m_dwRunState & RS_SUSPENDONIDLE) && (!m_fUserIsIdle))
{
MLLoadStringW(IDS_UPDATE_PAUSED, wszMsg, ARRAYSIZE(wszMsg));
hrStatus = WC_INTERNAL_S_PAUSED;
}
else
{
StrCpyW(wszMsg, L" "); // Don't say it, I know what you're thinking...
// ...if we don't do this, then the status
// text won't change.
hrStatus = WC_INTERNAL_S_PENDING;
}
NotifyHandlers(NH_UPDATEPROGRESS, &pItem->m_cookie, -1,
-1, -1, hrStatus, wszMsg);
}
}
}
m_fFillingTheQueue = FALSE;
}
}
HRESULT CThrottler::AddItemToListTail(CUpdateItem *pAddItem)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr = S_OK;
ASSERT(NULL != pAddItem);
if (NULL != pAddItem)
{
if (NULL == m_pItemsTail)
{
// Nothing in the list
ASSERT(NULL == m_pItemsHead);
m_pItemsHead = pAddItem;
}
else
{
m_pItemsTail->m_pNext = pAddItem;
}
m_pItemsTail = pAddItem;
}
else
{
hr = E_UNEXPECTED;
}
ASSERT(NULL != m_pItemsHead);
ASSERT(NULL != m_pItemsTail);
ASSERT(NULL == m_pItemsTail->m_pNext);
ASSERT(SUCCEEDED(hr));
return hr;
}
HRESULT CThrottler::RemoveItemFromList(CUpdateItem *pRemoveItem, BOOL fDelete)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr = E_UNEXPECTED;
CUpdateItem *pItem = m_pItemsHead;
CUpdateItem *pPrevItem = NULL;
ASSERT(NULL != pRemoveItem);
ASSERT(NULL != m_pItemsHead);
ASSERT(NULL != m_pItemsTail);
if (NULL != pRemoveItem)
{
int queueIndex = GetCookieIndexInQueue(&pRemoveItem->m_cookie);
if (queueIndex >= 0)
{
m_updateQueue[queueIndex] = NULL;
}
while (pItem)
{
if (pItem == pRemoveItem)
{
if (NULL != pPrevItem)
{
// Removing beyond the head
pPrevItem->m_pNext = pItem->m_pNext;
}
else
{
// Removing the head
m_pItemsHead = pItem->m_pNext;
}
// Now fix the tail
if (m_pItemsTail == pRemoveItem)
{
m_pItemsTail = pPrevItem;
}
hr = S_OK;
break;
}
pPrevItem = pItem;
pItem = pItem->m_pNext;
}
if (fDelete)
{
delete pRemoveItem;
}
}
ASSERT(((NULL != m_pItemsHead) && (NULL != m_pItemsTail) && (NULL == m_pItemsTail->m_pNext)) ||
((NULL == m_pItemsHead) && (NULL == m_pItemsTail)));
ASSERT(SUCCEEDED(hr));
// If we have just removed our last item from the list, check to see if we forced
// global online mode or autodialed and fix up if so.
if ((NULL == m_pItemsHead) && (m_fForcedGlobalOnline || m_fAutoDialed))
{
if (m_fForcedGlobalOnline)
{
SetGlobalOffline(TRUE);
m_fForcedGlobalOnline = FALSE;
m_fAutoDialed = FALSE;
}
else
{
ASSERT(m_fAutoDialed);
InternetAutodialHangup(0);
m_fAutoDialed=FALSE;
}
}
return hr;
}
HRESULT CThrottler::CanScheduledItemRun(ISubscriptionItem *pSubsItem)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
// If this item is running as a result of a schedule invocation, then
// we need to check time/range restrictions.
HRESULT hr = S_OK;
const TCHAR c_szNoScheduledUpdates[] = TEXT("NoScheduledUpdates");
DWORD dwData;
DWORD cbData = sizeof(dwData);
// First check if the user has disabled scheduled updates in inetcpl.
if ((ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, c_szRegKey,
c_szNoScheduledUpdates, NULL, &dwData, &cbData))
&& dwData)
{
hr = INET_E_SCHEDULED_UPDATES_DISABLED;
}
if (SUCCEEDED(hr))
{
// Check if admin has disabled scheduled updates altogether
if (SHRestricted2W(REST_NoScheduledUpdates, NULL, 0))
{
hr = INET_E_SCHEDULED_UPDATES_RESTRICTED;
}
}
if (SUCCEEDED(hr))
{
// Check if admin has set a minimum update interval.
DWORD dwMinUpdateInterval = SHRestricted2W(REST_MinUpdateInterval, NULL, 0);
if (dwMinUpdateInterval > 0)
{
DATE dt;
if (SUCCEEDED(ReadDATE(pSubsItem, c_szPropCompletionTime, &dt)))
{
SYSTEMTIME st;
GetLocalTime(&st);
CFileTime lastUpdate;
CFileTime currentTime;
VariantTimeToFileTime(dt, lastUpdate);
SystemTimeToFileTime(&st, &currentTime);
if ((currentTime - lastUpdate) <
((__int64)dwMinUpdateInterval * ONE_MINUTE_IN_FILETIME))
{
hr = INET_E_SCHEDULED_UPDATE_INTERVAL;
}
}
}
}
if (SUCCEEDED(hr))
{
DWORD dwBegin = SHRestricted2W(REST_UpdateExcludeBegin, NULL, 0);
DWORD dwEnd = SHRestricted2W(REST_UpdateExcludeEnd, NULL, 0);
// Check if admin has specified a blackout time for scheduled updates.
if (dwBegin && dwEnd)
{
SYSTEMTIME st;
CFileTime ftNow,
ftBegin,
ftEnd;
GetLocalTime(&st);
SystemTimeToFileTime(&st, &ftNow);
st.wSecond = 0;
st.wMilliseconds = 0;
st.wHour = (WORD)dwBegin / 60;
st.wMinute = (WORD)dwBegin % 60;
SystemTimeToFileTime(&st, &ftBegin);
st.wHour = (WORD)dwEnd / 60;
st.wMinute = (WORD)dwEnd % 60;
SystemTimeToFileTime(&st, &ftEnd);
// if these values are normalized (ie. begin comes before end)
if (ftBegin <= ftEnd)
{
// Then just check to see if time now is between begin
// and end. (ie. ftEnd >= ftNow >= ftBegin)
if ((ftNow >= ftBegin) && (ftNow <= ftEnd))
{
hr = INET_E_SCHEDULED_EXCLUDE_RANGE;
}
}
else
{
// Begin and end are not normalized. So we check to see if
// now is before end or now is after begin.
// For example:
// Assuming begin is 6pm and end is 6am. If now is 5 pm, the
// item should run. If now is 10pm or 4am, it should not run.
if ((ftNow <= ftEnd) || (ftNow >= ftBegin))
{
hr = INET_E_SCHEDULED_EXCLUDE_RANGE;
}
}
}
}
return hr;
}
HRESULT CThrottler::RunCookies(DWORD dwNumCookies,
const SUBSCRIPTIONCOOKIE *pSubscriptionCookies,
DWORD dwSyncFlags)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr = S_OK;
DWORD i;
CUpdateItem *pUpdateItem;
DWORD nValidCookies;
ASSERT(NULL != m_pSyncMgrs);
ASSERT(0 != dwNumCookies);
ASSERT(NULL != pSubscriptionCookies);
if ((0 == dwNumCookies) ||
(NULL == pSubscriptionCookies))
{
return E_INVALIDARG;
}
// Check for global offline mode.
if (!m_fForcedGlobalOnline && IsGlobalOffline())
{
// Force global online mode so that our update will succeed.
DBG("CThrottler::RunCookies; forcing global online mode");
SetGlobalOffline(FALSE);
m_fForcedGlobalOnline = TRUE;
}
if (IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_MANUAL) ||
IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_INVOKE))
{
if (!InternetGetConnectedStateEx(NULL, NULL, 0, 0))
{
if (!InternetAutodial(INTERNET_AUTODIAL_FORCE_ONLINE, 0))
{
// REARCHITECT clean up this extra addref/release/return stuff
AddRef();
DBG("CThrottler::RunCookies autodial failed");
// Uh-oh. The user cancelled the dial after starting a
// manual update. Clean up and return.
if (m_fForcedGlobalOnline)
{
SetGlobalOffline(TRUE);
m_fForcedGlobalOnline=FALSE;
}
WCHAR wszMsg[256];
MLLoadStringW(IDS_STATUS_ABORTED, wszMsg, ARRAYSIZE(wszMsg));
for (i=0; i<dwNumCookies; i++)
{
ReportThrottlerError(&pSubscriptionCookies[i], E_ABORT, wszMsg);
UpdateEnd(&pSubscriptionCookies[i], 0, E_ABORT, wszMsg);
}
Release();
return S_FALSE; // E_ABORT;
}
// Autodial succeeded
m_fAutoDialed = TRUE;
}
}
SUBSCRIPTIONCOOKIE *pCookies = new SUBSCRIPTIONCOOKIE[dwNumCookies];
if (NULL != pCookies)
{
SUBSCRIPTIONCOOKIE *pCookie = pCookies;
memcpy(pCookies, pSubscriptionCookies, dwNumCookies * sizeof(SUBSCRIPTIONCOOKIE));
// ************************************************************************
// Don't add any return statements in the loop! We keep a ref on ourselves
// during this call in case we are Released by all of the sync handlers.
// ************************************************************************
AddRef();
nValidCookies = 0;
for (i = 0; i < dwNumCookies; i++, pCookie++)
{
if (*pCookie == GUID_NULL)
{
continue;
}
nValidCookies++;
if (IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_IDLE))
{
m_fUserIsIdle = TRUE;
}
if (SUCCEEDED(FindCookie(pCookie, &pUpdateItem)))
{
if (IsIgnoreIdleSyncEvent(dwSyncFlags))
{
DUMPITEM("Removing RS_SUSPENDONIDLE in CThrottler::RunCookies", pCookie);
// Items updated manually are no longer subject to idle detection.
pUpdateItem->m_dwRunState &= ~RS_SUSPENDONIDLE;
}
if (IsSyncEventFlag(dwSyncFlags, SYNCMGRFLAG_MAYBOTHERUSER))
{
// We may now bother user for this item
pUpdateItem->m_dwRunState |= RS_MAYBOTHERUSER;
}
}
else
{
ISubscriptionItem *psi;
HRESULT hrItem = SubscriptionItemFromCookie(FALSE, pCookie, &psi);
if (SUCCEEDED(hrItem))
{
SUBSCRIPTIONITEMINFO sii;
sii.cbSize = sizeof(SUBSCRIPTIONITEMINFO);
hrItem = psi->GetSubscriptionItemInfo(&sii);
if (SUCCEEDED(hrItem))
{
DWORD dwRunState = RS_READY;
if (IsSyncEvent(dwSyncFlags, SYNCMGRFLAG_IDLE))
{
dwRunState |= RS_SUSPENDONIDLE;
}
if (IsSyncEventFlag(dwSyncFlags, SYNCMGRFLAG_MAYBOTHERUSER))
{
dwRunState |= RS_MAYBOTHERUSER;
}
if (IsScheduleSyncEvent(dwSyncFlags))
{
hrItem = CanScheduledItemRun(psi);
}
if (SUCCEEDED(hrItem))
{
pUpdateItem = new CUpdateItem(*pCookie, dwRunState);
if (NULL != pUpdateItem)
{
AddItemToListTail(pUpdateItem);
}
else
{
hrItem = E_OUTOFMEMORY;
}
}
}
psi->Release();
}
if (FAILED(hrItem))
{
// If we fail on an item, we will continue to try others, but
// we need to indicate failure for this one.
FailedUpdate(hrItem, pCookie);
hr = S_FALSE;
}
}
if (NULL == m_pSyncMgrs)
{
// We have been unadvised!
break;
}
}
// No point in trying to update if nobody wants to listen
if (NULL != m_pSyncMgrs)
{
FillTheQueue();
}
Release();
delete [] pCookies;
if (0 == nValidCookies)
{
hr = E_FAIL;
}
// ************************************************************************
// No member variable access after this since we could be dead!!!!
// ************************************************************************
}
else
{
hr = E_OUTOFMEMORY;
}
return hr;
}
HRESULT CThrottler::FindCookie(
const SUBSCRIPTIONCOOKIE *pSubscriptionCookie,
CUpdateItem **ppUpdateItem)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr = E_FAIL;
CUpdateItem *pItem = m_pItemsHead;
ASSERT(NULL != ppUpdateItem);
*ppUpdateItem = NULL;
while (pItem)
{
if (pItem->m_cookie == *pSubscriptionCookie)
{
*ppUpdateItem = pItem;
hr = S_OK;
break;
}
pItem = pItem->m_pNext;
}
return hr;
}
//==============================================================================
//
// Auto cache size increase
//
//==============================================================================
// We can return:
// E_PENDING - agent will pause and wait to be resumed or aborted
// INET_S_AGENT_INCREASED_CACHE_SIZE - agent will try making stuff sticky again
// anything else - agent will abort with INET_E_AGENT_CACHE_SIZE_EXCEEDED
HRESULT CThrottler::AutoCacheSizeRequest(
const SUBSCRIPTIONCOOKIE *pSubscriptionCookie)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
HRESULT hr = S_OK;
DWORD dwCacheSizeKB;
int queueIndex;
DWORD dwValue, dwSize = sizeof(dwValue);
if (ERROR_SUCCESS == SHGetValue(HKEY_CURRENT_USER, c_szKeyRestrict, c_szCache, NULL, &dwValue, &dwSize)
&& (dwValue != 0))
{
// Not allowed to change the cache size.
hr = E_FAIL;
}
queueIndex = GetCookieIndexInQueue(pSubscriptionCookie);
if (queueIndex >= 0)
{
if (!(m_updateQueue[queueIndex]->m_dwRunState & RS_MAYBOTHERUSER))
{
// We're not allowed to bother user. Fail.
hr = E_FAIL;
}
}
else
{
DBG_WARN("CThrottler::AutoCacheSizeRequest couldn't find cookie in run queue.");
hr = E_FAIL; // Couldn't find this cookie in our queue?!
}
if (SUCCEEDED(hr) && m_fAutoCacheSizePending)
{
// We're already asking the user to increase the cache size.
hr = E_PENDING;
}
if (SUCCEEDED(hr))
{
// Let's try to increase the cache.
if (SUCCEEDED(IncreaseCacheSize(&dwCacheSizeKB)))
{
hr = INET_S_AGENT_INCREASED_CACHE_SIZE;
}
else
{
// We need to ask the user.
if ((++ m_nAutoCacheSizeTimesAsked) > MAX_AUTOCACHESIZE_ASK)
{
hr = E_ABORT; // Already bothered them enough.
}
else
{
// Let's ask the user. We need unwind our call stack now, however.
// Tell the throttler to ask the user
if (SUCCEEDED(CreateThrottlerWnd()))
{
PostMessage(m_hwndThrottler, WM_THROTTLER_AUTOCACHESIZE_ASK, 0, dwCacheSizeKB);
m_fAutoCacheSizePending = TRUE;
hr = E_PENDING;
}
else
{
hr = E_FAIL;
}
}
}
} // !m_fAutoCacheSizePending
if (hr == E_PENDING)
{
// Mark this agent as paused.
int queueIndex = GetCookieIndexInQueue(pSubscriptionCookie);
ASSERT(queueIndex >= 0);
if (queueIndex >= 0)
{
m_updateQueue[queueIndex]->m_dwRunState &= ~RS_UPDATING;
m_updateQueue[queueIndex]->m_dwRunState |= RS_SUSPENDED;
m_updateQueue[queueIndex] = NULL;
}
}
return hr;
}
HRESULT CThrottler::AutoCacheSizeAskUser(DWORD dwCacheSizeKB)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
ASSERT(m_fAutoCacheSizePending);
ASSERT(dwCacheSizeKB);
ASSERT(m_hwndParent);
HRESULT hr = E_FAIL;
// Keep-Alive
AddRef();
if (IDOK == ShellMessageBox(MLGetHinst(),
m_hwndParent,
MAKEINTRESOURCE(IDS_CACHELIMIT_MESSAGE),
MAKEINTRESOURCE(IDS_CACHELIMIT_TITLE),
MB_OKCANCEL | MB_SETFOREGROUND | MB_ICONQUESTION))
{
// Come up with a good cache size increase and resume agents
m_dwAutoCacheSizeIncrease = dwCacheSizeKB / 4;
if (m_dwAutoCacheSizeIncrease < MIN_CACHE_INCREASE)
{
m_dwAutoCacheSizeIncrease = MIN_CACHE_INCREASE;
}
m_dwMaxAutoCacheSize = dwCacheSizeKB + (2 * m_dwAutoCacheSizeIncrease);
if (SUCCEEDED(IncreaseCacheSize(NULL)))
{
hr = S_OK;
}
}
else
{
// Abort agents
}
m_fAutoCacheSizePending = FALSE;
if (FAILED(hr))
{
// User said no (or we couldn't increase the cache).
ActuallyAbortAll();
}
else
{
FillTheQueue();
}
Release();
return hr;
}
// Auto-increase cache size if user previously ok'd it
HRESULT CThrottler::IncreaseCacheSize(DWORD *pdwNewCacheSizeKB)
{
ASSERT(GetCurrentThreadId() == m_dwThreadId);
LPINTERNET_CACHE_CONFIG_INFOA pCCI=NULL;
DWORD dwSizeInKB=0, dwPercent;
DWORD dwNewSizeInKB=0;
HRESULT hr = E_FAIL;
if (SUCCEEDED(GetCacheInfo(&pCCI, &dwSizeInKB, &dwPercent)))
{
if (dwSizeInKB < m_dwMaxAutoCacheSize)
{
ASSERT(m_dwAutoCacheSizeIncrease > 1023); // At least 1 meg
if (m_dwAutoCacheSizeIncrease)
{
// We still have room to increase cache without asking the user. Use it.
dwNewSizeInKB = dwSizeInKB + m_dwAutoCacheSizeIncrease;
if (dwNewSizeInKB > m_dwMaxAutoCacheSize)
{
dwNewSizeInKB = m_dwMaxAutoCacheSize;
}
if (SUCCEEDED(SetCacheSize(pCCI, dwNewSizeInKB)))
{
hr = S_OK;
dwSizeInKB = dwNewSizeInKB;
DBG("Throttler just increased TIF cache size");
}
}
}
MemFree(pCCI);
}
if (pdwNewCacheSizeKB)
{
*pdwNewCacheSizeKB = dwSizeInKB;
}
return hr;
}