/*++

Copyright (C) 2000-2001 Microsoft Corporation

Module Name:

    WmiCache.cpp

Abstract:

    WMI cache for _IWmiObject's.  Used within wbemcore only.  When memory threshold 
	is hit, objects are stored in disk-based store.  When size of disk-based store
	gets too big, additions to cache fail.

History:

    paulall		10-Mar-2000		Created.


--*/

#include "precomp.h"
#include "wbemint.h"
#include "wbemcli.h"
#include "WmiCache.h"
#include <arrtempl.h>


//***************************************************************************
//
//***************************************************************************
CWmiCache::CWmiCache()
: m_lRefCount(0), m_nEnum(0)
{
	InitializeCriticalSection(&m_cs);
}

//***************************************************************************
//
//***************************************************************************
CWmiCache::~CWmiCache()
{
	Empty(0, NULL);
	DeleteCriticalSection(&m_cs);
}

//***************************************************************************
//
//***************************************************************************
STDMETHODIMP CWmiCache::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
    *ppvObj = 0;

    if (IID_IUnknown==riid || IID__IWmiCache==riid)
    {
        *ppvObj = (_IWmiCache*)this;
        AddRef();
        return NOERROR;
    }

    return E_NOINTERFACE;

}

//***************************************************************************
//
//***************************************************************************
ULONG CWmiCache::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

//***************************************************************************
//
//***************************************************************************
ULONG CWmiCache::Release()
{
    ULONG uNewCount = InterlockedDecrement(&m_lRefCount);
    if (0 == uNewCount)
        delete this;
    return uNewCount;
}


//***************************************************************************
//
//***************************************************************************
STDMETHODIMP CWmiCache::SetConfigValue(
    /*[in]*/ ULONG uID,
    /*[in]*/ ULONG uValue
    )
{
	return E_NOTIMPL;
}

//***************************************************************************
//
//***************************************************************************
STDMETHODIMP CWmiCache::GetConfigValue(
    /*[in]*/  ULONG uID,
    /*[out]*/ ULONG *puValue
   )
{
	return E_NOTIMPL;
}

//***************************************************************************
//
//***************************************************************************
STDMETHODIMP CWmiCache::Empty(
    /*[in]*/ ULONG uFlags,
    /*[in]*/ LPCWSTR wszClass
    )
{
	EnterCriticalSection(&m_cs);

	while(m_objects.Size())
	{
		CWmiCacheObject *pObj = (CWmiCacheObject*)m_objects[m_objects.Size()-1];
		pObj->m_pObj->Release();
		delete pObj;
		m_objects.RemoveAt(m_objects.Size()-1);
	}

	LeaveCriticalSection(&m_cs);
	return WBEM_S_NO_ERROR;
}

//***************************************************************************
//
// Also subsumes replace functionality
//
// __PATH Property is used as a real key
//***************************************************************************
STDMETHODIMP CWmiCache::AddObject(
    /*[in]*/ ULONG uFlags,
    /*[in]*/ _IWmiObject *pObj
    )
{
	HRESULT hr = WBEM_S_NO_ERROR;

	int nPathHash = 0;
	hr = HashPath(pObj, &nPathHash);
	if (FAILED(hr))
		return hr;

	CWmiCacheObject *pCacheObj = new CWmiCacheObject(nPathHash, pObj);
	if (pCacheObj == NULL)
		return WBEM_E_OUT_OF_MEMORY;

	EnterCriticalSection(&m_cs);

	pObj->AddRef();

	int nRet = m_objects.Add(pCacheObj);
	if (nRet != CFlexArray::no_error)
	{
		pObj->Release();
		if (nRet == CFlexArray::out_of_memory)
			hr = WBEM_E_OUT_OF_MEMORY;
		else
			hr = WBEM_E_FAILED;
	}

	LeaveCriticalSection(&m_cs);
	return hr;
}

//***************************************************************************
//
//***************************************************************************
STDMETHODIMP CWmiCache::DeleteByPath(
    /*[in]*/ ULONG uFlags,
    /*[in]*/ LPCWSTR wszFullPath
    )
{
	HRESULT hr;
	int nPathHash = 0;
	hr = HashPath(wszFullPath, &nPathHash);
	if (FAILED(hr))
		return hr;

	hr = WBEM_E_NOT_FOUND;

	EnterCriticalSection(&m_cs);

	for (int i = 0; i != m_objects.Size(); i++)
	{
		CWmiCacheObject *pObj = (CWmiCacheObject*)m_objects[i];

		if (pObj->m_nHash = nPathHash)
		{
			hr = ComparePath(wszFullPath, pObj->m_pObj);
			if (SUCCEEDED(hr))
			{
				m_objects.RemoveAt(i);
				pObj->m_pObj->Release();
				delete pObj;
				hr = WBEM_S_NO_ERROR;
				break;
			}
			else if (hr != WBEM_E_NOT_FOUND)
				break;
		}
	}

	LeaveCriticalSection(&m_cs);
	return hr;
}

//***************************************************************************
//
//***************************************************************************
STDMETHODIMP CWmiCache::DeleteByPointer(
    /*[in]*/ ULONG uFlags,
    /*[in]*/ _IWmiObject *pTarget
    )
{
	HRESULT hr = WBEM_E_NOT_FOUND;
	EnterCriticalSection(&m_cs);

	for (int i = 0; i != m_objects.Size(); i++)
	{
		if (m_objects[i] == pTarget)
		{
			m_objects.RemoveAt(i);
			hr = WBEM_S_NO_ERROR;
			break;
		}
	}

	LeaveCriticalSection(&m_cs);

	return hr;
}

//***************************************************************************
//
//***************************************************************************
STDMETHODIMP CWmiCache::GetByPath(
    /*[in]*/ ULONG uFlags,
    /*[in]*/ LPCWSTR wszFullPath,
    /*[out]*/ _IWmiObject **pObj
    )
{
	HRESULT hr;
	int nPathHash = 0;
	hr = HashPath(wszFullPath, &nPathHash);
	if (FAILED(hr))
		return hr;

	hr = WBEM_E_NOT_FOUND;

	EnterCriticalSection(&m_cs);

	for (int i = 0; i != m_objects.Size(); i++)
	{
		CWmiCacheObject *pCacheObj = (CWmiCacheObject*)m_objects[i];

		if (pCacheObj->m_nHash == nPathHash)
		{
			hr = ComparePath(wszFullPath, pCacheObj ->m_pObj);
			if (SUCCEEDED(hr))
			{
				*pObj = pCacheObj->m_pObj;
				(*pObj)->AddRef();
				hr = WBEM_S_NO_ERROR;
				break;
			}
			else if (hr != WBEM_E_NOT_FOUND)
			{
				break;
			}
		}
	}

	LeaveCriticalSection(&m_cs);

	return hr;
}

//***************************************************************************
//
// Filters: uFlags==0==all, uFlags==WMICACHE_CLASS_SHALLOW, WMICACHE_CLASS_DEEP
//***************************************************************************
STDMETHODIMP CWmiCache::BeginEnum(
    /*[in]*/ ULONG uFlags,
    /*[in]*/ LPCWSTR wszFilter
    )
{
	if (uFlags != 0)
		return WBEM_E_INVALID_PARAMETER;

	m_nEnum = 0;
	return WBEM_S_NO_ERROR;
}

//***************************************************************************
//
//***************************************************************************
STDMETHODIMP CWmiCache::Next(
    /*[in]*/ ULONG uBufSize,
    /*[out, size_is(uBufSize), length_is(*puReturned)]*/ _IWmiObject **pObjects,
    /*[out]*/ ULONG *puReturned
    )
{
	HRESULT hr = WBEM_S_NO_ERROR;
	EnterCriticalSection(&m_cs);
	
	for (ULONG i = 0; i != uBufSize; i++, m_nEnum++)
	{
		if (m_nEnum == m_objects.Size())
		{
			hr = WBEM_S_NO_MORE_DATA;
			break;
		}
		pObjects[i] = ((CWmiCacheObject*)m_objects[m_nEnum])->m_pObj;
		pObjects[i]->AddRef();
	}

	*puReturned = i;

	LeaveCriticalSection(&m_cs);

	return hr;
}

HRESULT CWmiCache::HashPath(const wchar_t *wszPath, int *pnHash)
{
	DWORD dwHash = 0;

	while (*wszPath)
	{
		dwHash = (dwHash << 4) + *wszPath++;
		DWORD dwTemp = dwHash & 0xF0000000;
		if (dwTemp)
			dwHash ^= dwTemp >> 24;
		dwHash &= ~dwTemp;
	}


	*pnHash = (int)dwHash;
	return WBEM_S_NO_ERROR;;
}

HRESULT CWmiCache::HashPath(_IWmiObject *pObj, int *pnHash)
{
	VARIANT varPath;
	VariantInit(&varPath);
	HRESULT hr = pObj->Get(L"__PATH", 0, &varPath, NULL, NULL);
	if (SUCCEEDED(hr))
	{
		hr = HashPath(V_BSTR(&varPath), pnHash);
		VariantClear(&varPath);
	}
	return hr;
}

HRESULT CWmiCache::ComparePath(const wchar_t *wszPath, _IWmiObject *pObject)
{
	VARIANT varPath;
	VariantInit(&varPath);
	HRESULT hr = pObject->Get(L"__PATH", 0, &varPath, NULL, NULL);
	if (SUCCEEDED(hr))
	{
		if (wcscmp(wszPath, V_BSTR(&varPath)) == 0)
			hr = WBEM_S_NO_ERROR;
		else 
			hr = WBEM_E_NOT_FOUND;
		VariantClear(&varPath);
	}
	return hr;
}