1874 lines
49 KiB
C++
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, ¤tTime);
|
|
|
|
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;
|
|
}
|
|
|