/*===================================================================
Microsoft Denali

Microsoft Confidential.
Copyright 1997 Microsoft Corporation. All Rights Reserved.

Component: MetaUtil object

File: MUtilObj.cpp

Owner: t-BrianM

This file contains implementation of the main MetaUtil class.
Except CheckSchema is in ChkSchm.cpp and CheckKey is in ChkKey.cpp
===================================================================*/

#include "stdafx.h"
#include "MetaUtil.h"
#include "MUtilObj.h"
#include "keycol.h"

/*------------------------------------------------------------------
 * C M e t a U t i l  (edit and general portions)
 */

/*===================================================================
CMetaUtil::CMetaUtil

Constructor

Parameters:
	None

Returns:
	Nothing
===================================================================*/
CMetaUtil::CMetaUtil() : m_dwMaxPropSize(10 * 1024), //  10k
						 m_dwMaxKeySize(100 * 1024), // 100k
						 m_dwMaxNumErrors(100)
{
}

/*===================================================================
CMetaUtil::FinalConstruct

Constructor

Parameters:
	None

Returns:
	Nothing
===================================================================*/
HRESULT CMetaUtil::FinalConstruct() 
{
	HRESULT hr;

	// Create the metabase admin base object
	hr = ::CoCreateInstance(CLSID_MSAdminBase,
						    NULL,
						    CLSCTX_ALL,
					        IID_IMSAdminBase,
						    (void **)&m_pIMeta);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Create a schema table
	m_pCSchemaTable = new CMetaSchemaTable;
	if (m_pCSchemaTable == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}
	
	return S_OK;
}

/*===================================================================
CMetaUtil::FinalRelease

Destructor

Parameters:
	None

Returns:
	Nothing
===================================================================*/
void CMetaUtil::FinalRelease() 
{
	m_pIMeta = NULL;

	if (m_pCSchemaTable != NULL)
		m_pCSchemaTable->Release();
}

/*===================================================================
CMetaUtil::InterfaceSupportsErrorInfo

Standard ATL implementation

===================================================================*/

STDMETHODIMP CMetaUtil::InterfaceSupportsErrorInfo(REFIID riid)
{
	static const IID* arr[] = 
	{
		&IID_IMetaUtil,
	};
	for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
	{
		if (InlineIsEqualGUID(*arr[i],riid))
			return S_OK;
	}

	return S_FALSE;
}

/*===================================================================
CMetaUtil::EnumKeys

Do a flat (non-recursive) enumeration of subkeys

Parameters:
    bstrBaseKey	[in] Key to enumerate the subkeys of
	ppIReturn	[out, retval] interface for the ouput key collection

Returns:
	E_OUTOFMEMORY if allocation fails.
	E_INVALIDARG if ppIReturn == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::EnumKeys(BSTR bstrBaseKey, 
								 IKeyCollection ** ppIReturn)
{
	TRACE0("MetaUtil: CMetaUtil::EnumKeys\n");

	ASSERT_NULL_OR_POINTER(bstrBaseKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(ppIReturn, IKeyCollection *);

	if (ppIReturn == NULL) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	HRESULT hr;

	// Create the Flat Keys Collection
	CComObject<CFlatKeyCollection> *pObj = NULL;
	ATLTRY(pObj = new CComObject<CFlatKeyCollection>);
	if (pObj == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}
	hr = pObj->Init(m_pIMeta, OLE2T(bstrBaseKey));
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Set the interface to IKeyCollection
	hr = pObj->QueryInterface(IID_IKeyCollection, (void **) ppIReturn);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}
	ASSERT(ppIReturn != NULL);

	return S_OK;
}

/*===================================================================
CMetaUtil::EnumAllKeys

Do a deep (recursive) enumeration of subkeys

Parameters:
    bstrBaseKey	[in] Key to enumerate the subkeys of
	ppIReturn	[out, retval] interface for the ouput key collection

Returns:
	E_OUTOFMEMORY if allocation fails.
	E_INVALIDARG if ppIReturn == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::EnumAllKeys(BSTR bstrBaseKey, 
									IKeyCollection ** ppIReturn)
{
	TRACE0("MetaUtil: CMetaUtil::EnumAllKeys\n");

	ASSERT_NULL_OR_POINTER(bstrBaseKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(ppIReturn, IKeyCollection *);

	if (ppIReturn == NULL) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	HRESULT hr;

	// Create the Flat Keys Collection
	CComObject<CDeepKeyCollection> *pObj = NULL;
	ATLTRY(pObj = new CComObject<CDeepKeyCollection>);
	if (pObj == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}
	hr = pObj->Init(m_pIMeta, OLE2T(bstrBaseKey));
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Set the interface to IKeyCollection
	hr = pObj->QueryInterface(IID_IKeyCollection, (void **) ppIReturn);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}
	ASSERT(ppIReturn != NULL);

	return S_OK;
}

/*===================================================================
CMetaUtil::EnumProperties

Do an enumeration of properties

Parameters:
    bstrBaseKey	[in] Key to enumerate the properties of
	ppIReturn	[out, retval] interface for the ouput property collection

Returns:
	E_OUTOFMEMORY if allocation fails.
	E_INVALIDARG if ppIReturn == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::EnumProperties(BSTR bstrKey, 
									   IPropertyCollection **ppIReturn)
{
	TRACE0("MetaUtil: CMetaUtil::EnumProperties\n");

	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(ppIReturn, IKeyCollection *);

	if (ppIReturn == NULL) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	HRESULT hr;

	// Create the Flat Keys Collection
	CComObject<CPropertyCollection> *pObj = NULL;
	ATLTRY(pObj = new CComObject<CPropertyCollection>);
	if (pObj == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}
	hr = pObj->Init(m_pIMeta, m_pCSchemaTable, OLE2T(bstrKey));
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Set the interface to IPropertyCollection
	hr = pObj->QueryInterface(IID_IPropertyCollection, (void **) ppIReturn);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}
	ASSERT(ppIReturn != NULL);

	return S_OK;
}

/*===================================================================
CMetaUtil::CreateKey

Create a new key

Parameters:
    bstrKey		[in] Key to create

Returns:
	E_INVALIDARG if bstrKey == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::CreateKey(BSTR bstrKey)
{
	TRACE0("MetaUtil: CMetaUtil::CreateKey\n");

	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);

	if (bstrKey == NULL) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszKey[ADMINDATA_MAX_NAME_LEN];

	_tcscpy(tszKey,OLE2T(bstrKey));
	CannonizeKey(tszKey);

	return ::CreateKey(m_pIMeta, tszKey);
}

/*===================================================================
CMetaUtil::DeleteKey

Delete a key

Parameters:
    bstrKey		[in] Key to delete

Returns:
	E_INVALIDARG if bstrKey == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::DeleteKey(BSTR bstrKey)
{
	TRACE0("MetaUtil: CMetaUtil::DeleteKey\n");

	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);

	if (bstrKey == NULL) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszKey[ADMINDATA_MAX_NAME_LEN];

	_tcscpy(tszKey,OLE2T(bstrKey));
	CannonizeKey(tszKey);

	return ::DeleteKey(m_pIMeta, tszKey);
}

/*===================================================================
CMetaUtil::RenameKey

Rename a key

Parameters:
    bstrOldName		[in] Original Key Name
	bstrNewName		[in] New key name

Returns:
	E_INVALIDARG if bstrOldName == NULL OR bstrNewName == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::RenameKey(BSTR bstrOldName, BSTR bstrNewName)
{
	TRACE0("MetaUtil: CMetaUtil::RenameKey\n");
	ASSERT_NULL_OR_POINTER(bstrOldName, OLECHAR);
	ASSERT_NULL_OR_POINTER(bstrNewName, OLECHAR);

	if ((bstrOldName == NULL) || (bstrNewName == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	HRESULT hr;
	TCHAR tszOldName[ADMINDATA_MAX_NAME_LEN];
	TCHAR tszNewName[ADMINDATA_MAX_NAME_LEN];

	_tcscpy(tszOldName, OLE2T(bstrOldName));
	CannonizeKey(tszOldName);

	_tcscpy(tszNewName, OLE2T(bstrNewName));
	CannonizeKey(tszNewName);

	// Figure out the key's common root
	TCHAR tszParent[ADMINDATA_MAX_NAME_LEN];
	int i;

	i = 0;
	while ((tszOldName[i] != _T('\0')) && (tszNewName[i] != _T('\0')) &&
		   (tszOldName[i] == tszNewName[i])) {
		tszParent[i] = tszOldName[i];
		i++;
	}
	if (i == 0) {
		// Nothing in common
		tszParent[i] = _T('\0');
	}
	else {
		// Back up to the first slash
		while ((i > 0) && (tszParent[i] != _T('/'))) {
			i--;
		}

		// Cut it off at the slash
		tszParent[i] = _T('\0');
	}

	int iParentKeyLen;
	iParentKeyLen = _tcslen(tszParent);

	LPTSTR tszRelOldName;
	LPTSTR tszRelNewName;

	// Figure out the relative new and old names
	tszRelOldName = tszOldName + iParentKeyLen;
	if (*tszRelOldName == _T('/')) {
		tszRelOldName++;
	}

	tszRelNewName = tszNewName + iParentKeyLen;
	if (*tszRelNewName == _T('/')) {
		tszRelNewName++;
	}

	// Open the parent
	METADATA_HANDLE hMDParentKey;

	hr = m_pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
						   T2W(tszParent),
						   METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE,
					       MUTIL_OPEN_KEY_TIMEOUT,
						   &hMDParentKey);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Rename the key
	hr = m_pIMeta->RenameKey(hMDParentKey,
							 T2W(tszRelOldName), 
							 T2W(tszRelNewName));
	if (FAILED(hr)) {
		m_pIMeta->CloseKey(hMDParentKey);
		return ::ReportError(hr);
	}

	// Close the parent
	m_pIMeta->CloseKey(hMDParentKey);

	return S_OK;
}

/*===================================================================
CMetaUtil::CopyKey

Copy a key

Parameters:
    bstrSrcKey		[in] Source Key Name
	bstrDestKey		[in] Destination key name
	fOverwrite		[in] If true then already existing properties
					at destination are overwritten.

Returns:
	E_INVALIDARG if bstrSrcKey == NULL OR bstrDestKey == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::CopyKey(BSTR bstrSrcKey, BSTR bstrDestKey, BOOL fOverwrite)
{
	TRACE0("MetaUtil: CMetaUtil::CopyKey\n");
	ASSERT_NULL_OR_POINTER(bstrSrcKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(bstrDestKey, OLECHAR);

	if ((bstrSrcKey == NULL) || (bstrDestKey == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszSrcKey[ADMINDATA_MAX_NAME_LEN];
	TCHAR tszDestKey[ADMINDATA_MAX_NAME_LEN];

	_tcscpy(tszSrcKey, OLE2T(bstrSrcKey));
	CannonizeKey(tszSrcKey);

	_tcscpy(tszDestKey, OLE2T(bstrDestKey));
	CannonizeKey(tszDestKey);

	return ::CopyKey(m_pIMeta, tszSrcKey, tszDestKey, fOverwrite, TRUE);
}

/*===================================================================
CMetaUtil::MoveKey

Move a key

Parameters:
    bstrSrcKey		[in] Source Key Name
	bstrDestKey		[in] Destination key name
	fOverwrite		[in] If true then already existing properties
					at destination are overwritten.

Returns:
	E_INVALIDARG if bstrSrcKey == NULL OR bstrDestKey == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::MoveKey(BSTR bstrSrcKey, BSTR bstrDestKey, BOOL fOverwrite)
{
	TRACE0("MetaUtil: CMetaUtil::MoveKey\n");
	ASSERT_NULL_OR_POINTER(bstrSrcKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(bstrDestKey, OLECHAR);

	if ((bstrSrcKey == NULL) || (bstrDestKey == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszSrcKey[ADMINDATA_MAX_NAME_LEN];
	TCHAR tszDestKey[ADMINDATA_MAX_NAME_LEN];

	_tcscpy(tszSrcKey, OLE2T(bstrSrcKey));
	CannonizeKey(tszSrcKey);

	_tcscpy(tszDestKey, OLE2T(bstrDestKey));
	CannonizeKey(tszDestKey);

	return ::CopyKey(m_pIMeta, tszSrcKey, tszDestKey, fOverwrite, FALSE);
}

/*===================================================================
CMetaUtil::GetProperty

Gets a property object from the metabase.

Parameters:
    bstrKey		[in] Key containing property to get
	varId		[in] Identifier of property to get.  Either the 
				Id (number) or Name (string).
	ppIReturn	[out, retval] Interface for retreived property.
	
Returns:
	E_INVALIDARG if bstrKey == NULL or ppIReturn == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::GetProperty(BSTR bstrKey, 
									VARIANT varId, 
									IProperty **ppIReturn)
{
	TRACE0("MetaUtil: CMetaUtil::GetProperty\n");

	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(ppIReturn, IProperty *);

	if ((bstrKey == NULL) || (ppIReturn == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszKey[ADMINDATA_MAX_NAME_LEN];

	_tcscpy(tszKey,OLE2T(bstrKey));
	CannonizeKey(tszKey);

	return ::GetProperty(m_pIMeta, m_pCSchemaTable, tszKey, varId, ppIReturn);
}

/*===================================================================
CMetaUtil::CreateProperty

Creates a property object that can be written to the Metbase or
retreives the property if it already exists.

Parameters:
    bstrKey		[in] Key containing property to get
	varId		[in] Identifier of property to get.  Either the 
				Id (number) or Name (string).
	ppIReturn	[out, retval] Interface for retreived property.
	
Returns:
	E_INVALIDARG if bstrKey == NULL or ppIReturn == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::CreateProperty(BSTR bstrKey, 
									   VARIANT varId, 
									   IProperty **ppIReturn)
{
	TRACE0("MetaUtil: CMetaUtil::CreateProperty\n");

	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(ppIReturn, IProperty *);

	if ((bstrKey == NULL) || (ppIReturn == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszKey[ADMINDATA_MAX_NAME_LEN];

	_tcscpy(tszKey,OLE2T(bstrKey));
	CannonizeKey(tszKey);

	return ::CreateProperty(m_pIMeta, m_pCSchemaTable, tszKey, varId, ppIReturn);
}

/*===================================================================
CMetaUtil::DeleteProperty

Deletes a property from the metabase.

Parameters:
    bstrKey		[in] Key containing property to get
	varId		[in] Identifier of property to get.  Either the 
				Id (number) or Name (string).
	
Returns:
	E_INVALIDARG if bstrKey == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::DeleteProperty(BSTR bstrKey, VARIANT varId)
{
	TRACE0("MetaUtil: CMetaUtil::DeleteProperty\n");

	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);

	if (bstrKey == NULL) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszKey[ADMINDATA_MAX_NAME_LEN];

	_tcscpy(tszKey,OLE2T(bstrKey));
	CannonizeKey(tszKey);

	return ::DeleteProperty(m_pIMeta, m_pCSchemaTable, tszKey, varId);
}

/*===================================================================
CMetaUtil::ExpandString

Expands a string with environment variables.  Maximum output is 1024
bytes.

Parameters:
    bstrIn		[in] String to expand
	pbstrRet	[out, retval] Expanded string
	
Returns:
	E_INVALIDARG if bstrIn == NULL or pbstrRet == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::ExpandString(BSTR bstrIn, BSTR *pbstrRet)
{
	ASSERT_POINTER(bstrIn, OLECHAR);
	ASSERT_NULL_OR_POINTER(pbstrRet, BSTR);

	if ((bstrIn == NULL) || (pbstrRet == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszRet[1024];
	int iRet;

	iRet = ExpandEnvironmentStrings(OLE2T(bstrIn), tszRet, 1024);
	if (iRet == 0) {
		::ReportError(GetLastError());
	}

	*pbstrRet = T2BSTR(tszRet);

	return S_OK;
}

/*===================================================================
MetaUtil::PropIdToName

Converts a property Id to its name, as listed in 
_Machine_/Schema/Properties/Names

Parameters:
    bstrKey		[in] Approximate key where property is located, 
				needed to determine what schema to use.
	lId			[in] Id of property
	pbstrName	[out, retval] Output name of property
	
Returns:
	E_INVALIDARG if bstrKey == NULL or pbstrName == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::PropIdToName(BSTR bstrKey, long lId, BSTR *pbstrName)
{
	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(pbstrName, BSTR);

	if ((bstrKey == NULL) || (pbstrName == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszKey[ADMINDATA_MAX_NAME_LEN];
	CPropInfo *pCPropInfo;

	// Convert the base key to cannonical form
	_tcscpy(tszKey, OLE2T(bstrKey));
	CannonizeKey(tszKey);

	// Get the property info from the Schema Table
	pCPropInfo = m_pCSchemaTable->GetPropInfo(tszKey, lId);

	// Did we find it?  Is there a name entry?
	if ((pCPropInfo == NULL) || (pCPropInfo->GetName() == NULL)) {
		// No, return ""
		*pbstrName = T2BSTR(_T(""));
	}
	else {
		// Yes, return the name
		*pbstrName = T2BSTR(pCPropInfo->GetName());
	}
	return S_OK;
}

/*===================================================================
MetaUtil::PropNameToId

Converts a property name to its id, as listed in 
_Machine_/Schema/Properties/Names

Parameters:
    bstrKey		[in] Approximate key where property is located, 
				needed to determine what schema to use.
	pbstrName	[in] Name of property
	lId			[out, retval] Output id of property
	
Returns:
	E_INVALIDARG if bstrKey == NULL OR bstrName == NULL OR plId == NULL
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::PropNameToId(BSTR bstrKey, BSTR bstrName, long *plId)
{
	ASSERT_NULL_OR_POINTER(bstrKey, OLECHAR);
	ASSERT_NULL_OR_POINTER(bstrName, OLECHAR);
	ASSERT_NULL_OR_POINTER(plId, long);

	if ((bstrKey == NULL) || (bstrName == NULL) || (plId == NULL)) {
		return ::ReportError(E_INVALIDARG);
	}

	USES_CONVERSION;
	TCHAR tszKey[ADMINDATA_MAX_NAME_LEN];
	CPropInfo *pCPropInfo;

	// Convert the base key to cannonical form
	_tcscpy(tszKey, OLE2T(bstrKey));
	CannonizeKey(tszKey);

	// Get the property info from the Schema Table
	pCPropInfo = m_pCSchemaTable->GetPropInfo(tszKey, OLE2T(bstrName));

	// Did we find it?
	if (pCPropInfo == NULL) {
		// No, return 0
		*plId = 0;
	}
	else {
		// Yes, return the id
		*plId = pCPropInfo->GetId();
	}
	return S_OK;
}

/*===================================================================
MetaUtil::get_Config

Gets the value of a configuration setting.

Valid Settings:
	MaxPropertySize
	MaxKeySize
	MaxNumberOfErrors

Parameters:
    bstrSetting		[in] Name of the setting
	pvarValue		[out, retval] Value of the setting
	
Returns:
	E_INVALIDARG if bstrSettting doesn't match any known settings
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::get_Config(BSTR bstrSetting, VARIANT *pvarValue)
{
	ASSERT_POINTER(bstrSetting, OLECHAR);
	ASSERT_POINTER(pvarValue, VARIANT);

	USES_CONVERSION;
	LPTSTR tszSetting;

    if( !bstrSetting )
    {
        return ::ReportError(E_INVALIDARG);
    }

	VariantInit(pvarValue);
	tszSetting = OLE2T(bstrSetting);

	if (_tcsicmp(tszSetting, _T("MaxPropertySize")) == 0) {
		V_VT(pvarValue) = VT_I4;
		V_I4(pvarValue) = m_dwMaxPropSize;
	}
	else if (_tcsicmp(tszSetting, _T("MaxKeySize")) == 0) {
		V_VT(pvarValue) = VT_I4;
		V_I4(pvarValue) = m_dwMaxKeySize;
	}
	else if (_tcsicmp(tszSetting, _T("MaxNumberOfErrors")) == 0) {
		V_VT(pvarValue) = VT_I4;
		V_I4(pvarValue) = m_dwMaxNumErrors;
	}
	else {
		return ::ReportError(E_INVALIDARG);
	}

	return S_OK;
}

/*===================================================================
MetaUtil::put_Config

Sets the value of a configuration setting.

Valid Settings:
	MaxPropertySize
	MaxKeySize
	MaxNumberOfErrors

Parameters:
    bstrSetting		[in] Name of the setting
	varValue		[out, retval] New value of the setting
	
Returns:
	E_INVALIDARG if bstrSettting doesn't match any known settings or
		if varValue is of an unexpected subtype.
	S_OK on success
===================================================================*/
STDMETHODIMP CMetaUtil::put_Config(BSTR bstrSetting, VARIANT varValue)
{
	ASSERT_POINTER(bstrSetting, OLECHAR);

	USES_CONVERSION;
	HRESULT hr;
	LPTSTR tszSetting;

	tszSetting = OLE2T(bstrSetting);

	// Cleanup any IDispatch or byref stuff
	CComVariant varValue2;

	hr = VariantResolveDispatch(&varValue, &varValue2);
	if (FAILED(hr)) {
        return hr;
	}

	if (_tcsicmp(tszSetting, _T("MaxPropertySize")) == 0) {
		// Set Maximum Property Size
		switch (V_VT(&varValue2)) {
		
		case VT_I1:  case VT_I2:  case VT_I4: case VT_I8:
		case VT_UI1: case VT_UI2: case VT_UI8:

		// Coerce all integral types to VT_UI4
		if (FAILED(hr = VariantChangeType(&varValue2, &varValue2, 0, VT_UI4))) {
			return ::ReportError(hr);
			}

		// fallthru to VT_UI4

		case VT_UI4:

			m_dwMaxPropSize = V_UI4(&varValue2);
			break;

		default:

			// Unexpected data type
			return ::ReportError(E_INVALIDARG);
		}
	}
	else if (_tcsicmp(tszSetting, _T("MaxKeySize")) == 0) {
		// Set Maximum Key Size
		switch (V_VT(&varValue2)) {
		
		case VT_I1:  case VT_I2:  case VT_I4: case VT_I8:
		case VT_UI1: case VT_UI2: case VT_UI8:

		// Coerce all integral types to VT_UI4
		if (FAILED(hr = VariantChangeType(&varValue2, &varValue2, 0, VT_UI4))) {
			return ::ReportError(hr);
			}

		// fallthru to VT_UI4

		case VT_UI4:

			m_dwMaxKeySize = V_UI4(&varValue2);
			break;

		default:

			// Unexpected data type
			return ::ReportError(E_INVALIDARG);
		}
	}
	else if (_tcsicmp(tszSetting, _T("MaxNumberOfErrors")) == 0) {
		// Set Maximum Number of Errors
		switch (V_VT(&varValue2)) {
		
		case VT_I1:  case VT_I2:  case VT_I4: case VT_I8:
		case VT_UI1: case VT_UI2: case VT_UI8:

		// Coerce all integral types to VT_UI4
		if (FAILED(hr = VariantChangeType(&varValue2, &varValue2, 0, VT_UI4))) {
			return ::ReportError(hr);
			}

		// fallthru to VT_UI4

		case VT_UI4:

			m_dwMaxNumErrors = V_UI4(&varValue2);
			break;

		default:

			// Unexpected data type
			return ::ReportError(E_INVALIDARG);
		}
	}
	else {
		return ::ReportError(E_INVALIDARG);
	}

	return S_OK;
}

/*------------------------------------------------------------------
 * Methods also supported by the collections
 *
 * Actual implementation here to avoid redundant code
 */

/*===================================================================
CreateKey

Create a new key

Parameters:
	pIMeta		[in] Smart pointer to metabase, passed by reference
	            to avoid the copy and unneeded AddRef/Release.
				Would have used const, however the '->' operator
				would not work.
    tszKey		[in] Key to create

Returns:
	E_INVALIDARG if bstrKey == NULL
	S_OK on success
===================================================================*/
HRESULT CreateKey(CComPtr<IMSAdminBase> &pIMeta, 
				  LPCTSTR tszKey) 
{
	ASSERT(pIMeta.p != NULL);
	ASSERT_STRING(tszKey);

	USES_CONVERSION;
	HRESULT hr;

	TCHAR tszParent[ADMINDATA_MAX_NAME_LEN];
	TCHAR tszChild[ADMINDATA_MAX_NAME_LEN];

	::SplitKey(tszKey, tszParent, tszChild);

	// Open the parent key
	METADATA_HANDLE hMDParent;

	hr = pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
						 T2W(tszParent),
						 METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE,
					     MUTIL_OPEN_KEY_TIMEOUT,
						 &hMDParent);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Create the child
	hr = pIMeta->AddKey(hMDParent, T2W(tszChild)); 
	if (FAILED(hr)) {
		pIMeta->CloseKey(hMDParent);
		return ::ReportError(hr);
	}

	// Close the parent key
	pIMeta->CloseKey(hMDParent);

	return S_OK;
}

/*===================================================================
DeleteKey

Delete a key

Parameters:
	pIMeta		[in] Smart pointer to metabase, passed by reference
	            to avoid the copy and unneeded AddRef/Release.
				Would have used const, however the '->' operator
				would not work.
    tszKey		[in] Key to delete

Returns:
	E_INVALIDARG if pbSuccess == NULL
	S_OK on success
===================================================================*/
HRESULT DeleteKey(CComPtr<IMSAdminBase> &pIMeta, 
				  LPCTSTR tszKey) 
{
	ASSERT(pIMeta.p != NULL);
	ASSERT_STRING(tszKey);

	USES_CONVERSION;
	HRESULT hr;

	TCHAR tszParent[ADMINDATA_MAX_NAME_LEN];
	TCHAR tszChild[ADMINDATA_MAX_NAME_LEN];

	::SplitKey(tszKey, tszParent, tszChild);

	// Open the parent key
	METADATA_HANDLE hMDParent;

	hr = pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
						 T2W(tszParent),
						 METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE,
					     MUTIL_OPEN_KEY_TIMEOUT,
						 &hMDParent);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Delete the child
	hr = pIMeta->DeleteKey(hMDParent, T2W(tszChild)); 
	if (FAILED(hr)) {
		pIMeta->CloseKey(hMDParent);
		return ::ReportError(hr);
	}

	// Close the parent key
	pIMeta->CloseKey(hMDParent);

	return S_OK;
}

/*===================================================================
CMetaUtil::CopyKey

Copy or move a key

Parameters:
    bstrSrcKey		[in] Source Key Name
	bstrDestKey		[in] Destination key name
	fOverwrite		[in] If true then already existing properties
					at destination are overwritten.
	fCopy			[in] If true than copy the key, else move it

Returns:
	S_OK on success
===================================================================*/
HRESULT CopyKey(CComPtr<IMSAdminBase> &pIMeta, 
				LPTSTR tszSrcKey, 
				LPTSTR tszDestKey, 
				BOOL fOverwrite, 
				BOOL fCopy) 
{
	ASSERT(pIMeta.p != NULL);
	ASSERT_STRING(tszSrcKey);
	ASSERT_STRING(tszDestKey);

	USES_CONVERSION;
	HRESULT hr;


	// Check for overlap
	TCHAR tszParent[ADMINDATA_MAX_NAME_LEN];
	int i;

	i = 0;
	while ((tszSrcKey[i] != _T('\0')) && (tszDestKey[i] != _T('\0')) &&
		   (tszSrcKey[i] == tszDestKey[i])) {
		tszParent[i] = tszSrcKey[i];
		i++;
	}
    
    // Terminate tszParent
	tszParent[i] = _T('\0');

	if (i == 0) {
		// Nothing in common

		TCHAR tszSrcParent[ADMINDATA_MAX_NAME_LEN];
		TCHAR tszSrcChild[ADMINDATA_MAX_NAME_LEN];
		TCHAR tszDestParent[ADMINDATA_MAX_NAME_LEN];
		TCHAR tszDestChild[ADMINDATA_MAX_NAME_LEN];

		::SplitKey(tszSrcKey, tszSrcParent, tszSrcChild);
		::SplitKey(tszDestKey, tszDestParent, tszDestChild);

		// Open the parent source key
		METADATA_HANDLE hMDSrcParent;

		hr = pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
							 T2W(tszSrcParent),
							 METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE,
							 MUTIL_OPEN_KEY_TIMEOUT,
							 &hMDSrcParent);
		if (FAILED(hr)) {
			return ::ReportError(hr);
		}

		// Open the parent dest key
		METADATA_HANDLE hMDDestParent;

		hr = pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
							 T2W(tszDestParent),
							 METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE,
							 MUTIL_OPEN_KEY_TIMEOUT,
							 &hMDDestParent);
		if (FAILED(hr)) {
			return ::ReportError(hr);
		}


		// Copy the children
		hr = pIMeta->CopyKey(hMDSrcParent, T2W(tszSrcChild), 
							 hMDDestParent, T2W(tszDestChild), 
							 fOverwrite, fCopy);
		if (FAILED(hr)) {
			pIMeta->CloseKey(hMDSrcParent);
			pIMeta->CloseKey(hMDDestParent);
			return ::ReportError(hr);
		}

		// Close the parents
		pIMeta->CloseKey(hMDSrcParent);
		pIMeta->CloseKey(hMDDestParent);
	}
	else {
		// Something in common

		// Back up to the first slash
		while ((i > 0) && (tszParent[i] != _T('/'))) {
			i--;
		}

		// Cut it off at the slash
		tszParent[i] = _T('\0');

		int iParentKeyLen;
		iParentKeyLen = _tcslen(tszParent);

		LPTSTR tszSrcChild;
		LPTSTR tszDestChild;

		// Figure out the relative new and old names
		tszSrcChild = tszSrcKey + iParentKeyLen;
		if (*tszSrcChild == _T('/')) {
			tszSrcChild++;
		}

		tszDestChild = tszDestKey + iParentKeyLen;
		if (*tszDestChild == _T('/')) {
			tszDestChild++;
		}

		// Open the parent key
		METADATA_HANDLE hMDParent;

		hr = pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
							 T2W(tszParent),
							 METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE,
							 MUTIL_OPEN_KEY_TIMEOUT,
							 &hMDParent);
		if (FAILED(hr)) {
			return ::ReportError(hr);
		}


		// Copy the children
		hr = pIMeta->CopyKey(hMDParent, T2W(tszSrcChild), 
							 hMDParent, T2W(tszDestChild), 
							 fOverwrite, fCopy);
		if (FAILED(hr)) {
			pIMeta->CloseKey(hMDParent);
			return ::ReportError(hr);
		}

		// Close the parent
		pIMeta->CloseKey(hMDParent);
	}

	return S_OK;
}

/*===================================================================
GetProperty

Gets a property object from the metabase.

Parameters:
	pIMeta			[in] Smart pointer to metabase, passed by 
					reference to avoid the copy and unneeded 
					AddRef/Release.  Would have used const, however 
					the '->' operator would not work.
	pCSchemaTable	[in] Metabase schema table to use to look up 
					property names
    tszKey			[in] Key containing property to get
	varId			[in] Identifier of property to get.  Either the 
					Id (number) or Name (string).
	ppIReturn		[out, retval] Interface for retreived property.
	
Returns:
	S_OK on success
===================================================================*/
HRESULT GetProperty(CComPtr<IMSAdminBase> &pIMeta,
					CMetaSchemaTable *pCSchemaTable,
					LPCTSTR tszKey, 
					VARIANT varId, 
					IProperty **ppIReturn) 
{
	ASSERT(pIMeta != NULL);
	ASSERT_STRING(tszKey);
	ASSERT_POINTER(ppIReturn, IProperty *);

	HRESULT hr;
	DWORD dwId;

	// Figure out the property id
	hr = ::VarToMetaId(pCSchemaTable, tszKey, varId, &dwId);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Create the property object
	CComObject<CProperty> *pObj = NULL;
	ATLTRY(pObj = new CComObject<CProperty>);
	if (pObj == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}
	hr = pObj->Init(pIMeta, pCSchemaTable, tszKey, dwId, FALSE);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Set the interface to IProperty
	hr = pObj->QueryInterface(IID_IProperty, (void **) ppIReturn);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}
	ASSERT(*ppIReturn != NULL);

	return S_OK;
}

/*===================================================================
CreateProperty

Creates a property object that can be written to the Metbase or
retreives the property if it already exists.

Parameters:
	pIMeta			[in] Smart pointer to metabase, passed by 
					reference to avoid the copy and unneeded 
					AddRef/Release.  Would have used const, however 
					the '->' operator would not work.
	pCSchemaTable	[in] Metabase schema table to use to look up 
					property names
    tszKey			[in] Key containing property to get
	varId			[in] Identifier of property to get.  Either the 
					Id (number) or Name (string).
	ppIReturn		[out, retval] Interface for retreived property.
	
Returns:
	S_OK on success
===================================================================*/
HRESULT CreateProperty(CComPtr<IMSAdminBase> &pIMeta,
					   CMetaSchemaTable *pCSchemaTable,
					   LPCTSTR tszKey, 
					   VARIANT varId, 
					   IProperty **ppIReturn) 
{
	ASSERT(pIMeta.p != NULL);
	ASSERT_STRING(tszKey);
	ASSERT_POINTER(ppIReturn, IProperty *);

	HRESULT hr;
	DWORD dwId;

	// Figure out the property id
	hr = ::VarToMetaId(pCSchemaTable, tszKey, varId, &dwId);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Create the property object
	CComObject<CProperty> *pObj = NULL;
	ATLTRY(pObj = new CComObject<CProperty>);
	if (pObj == NULL) {
		return ::ReportError(E_OUTOFMEMORY);
	}
	hr = pObj->Init(pIMeta, pCSchemaTable, tszKey, dwId, TRUE);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Set the interface to IProperty
	hr = pObj->QueryInterface(IID_IProperty, (void **) ppIReturn);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}
	ASSERT(*ppIReturn != NULL);

	return S_OK;
}

/*===================================================================
DeleteProperty

Deletes a property from the metabase.

Parameters:
    pIMeta			[in] Smart pointer to metabase, passed by 
					reference to avoid the copy and unneeded 
					AddRef/Release.  Would have used const, however 
					the '->' operator would not work.
	pCSchemaTable	[in] Metabase schema table to use to look up 
					property names
    tszKey			[in] Key containing property to get
	varId			[in] Identifier of property to get.  Either the 
					Id (number) or Name (string).
	
Returns:
	S_OK on success
===================================================================*/
HRESULT DeleteProperty(CComPtr<IMSAdminBase> &pIMeta,
					   CMetaSchemaTable *pCSchemaTable,
					   LPTSTR tszKey, 
					   VARIANT varId) 
{
	ASSERT(pIMeta.p != NULL);
	ASSERT_STRING(tszKey);

	USES_CONVERSION;
	HRESULT hr;
	DWORD dwId;

	hr = ::VarToMetaId(pCSchemaTable, tszKey, varId, &dwId);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Open the key
	METADATA_HANDLE hMDKey;

	hr = pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE,
						 T2W(tszKey),
						 METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE,
					     MUTIL_OPEN_KEY_TIMEOUT,
						 &hMDKey);
	if (FAILED(hr)) {
		return ::ReportError(hr);
	}

	// Delete the property
	hr = pIMeta->DeleteData(hMDKey, NULL, dwId, ALL_METADATA); 
	if (FAILED(hr)) {
		pIMeta->CloseKey(hMDKey);
		return ::ReportError(hr);
	}

	// Close the key
	pIMeta->CloseKey(hMDKey);

	return S_OK;
}

/*===================================================================
VarToMetaId

Converts a variant to a metabase property id.  IDispatch is resolved,
strings are looked up in the schema property list and integers are
converted to a DWORD.

Parameters:
	pCSchemaTable	[in] Metabase schema table to use to look up 
					property names
    tszKey			[in] Key the property is under (needed to get the 
					right schema)
	varId			[in] Variant to resolve
	pdwId			[out] Metabase property Id that varId resolved to

Returns:
	E_INVALIDARG if varId subtype isn't an integer or string
	ERROR_FILE_NOT_FOUND if varId is a BSTR that doesn't match any
		property names.
	S_OK on success
===================================================================*/
HRESULT VarToMetaId(CMetaSchemaTable *pCSchemaTable,
					LPCTSTR tszKey, 
					VARIANT varId, 
					DWORD *pdwId) 
{
	ASSERT_STRING(tszKey);
	ASSERT_POINTER(pdwId, DWORD);

	USES_CONVERSION;
	HRESULT hr;
	CComVariant varId2;
	CPropInfo *pCPropInfo;

    // VBScript can call us with a VARIANT that isn't a simple type,
    // such as VT_VARIANT|VT_BYREF.  This resolves it to a simple type.
    if (FAILED(hr = VariantResolveDispatch(&varId, &varId2)))
        return hr;

    switch (V_VT(&varId2)) {

    case VT_BSTR:
        // Look up the property name
		pCPropInfo = pCSchemaTable->GetPropInfo(tszKey, OLE2T(V_BSTR(&varId2)));
		if (pCPropInfo == NULL) {
			return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
		}
		*pdwId = pCPropInfo->GetId();

		return S_OK;
        break;
        
    case VT_I1:  case VT_I2:  case VT_I4: case VT_I8:
    case VT_UI1: case VT_UI2: case VT_UI8:
        // Coerce all integral types to VT_UI4, which is the same as REG_DWORD
        if (FAILED(hr = VariantChangeType(&varId2, &varId2, 0, VT_UI4)))
            return hr;

        // fallthru to VT_UI4

    case VT_UI4:
		*pdwId = V_UI4(&varId2);
        break;

    default:
        return E_INVALIDARG;   // Cannot handle this data type
    }

	return S_OK;
}