/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    mb.cxx

Abstract:

    This module implements the MB class.

Author:

    Keith Moore (keithmo)        05-Feb-1997
        Moved from "inlines" in MB.HXX.

Revision History:

--*/


#include "imiscp.hxx"


//
//  Default timeout
//

#define MB_TIMEOUT           (30 * 1000)

//
//  Default timeout for SaveData
//

#define MB_SAVE_TIMEOUT      (10 * 1000)        // milliseconds

MB::MB( IMDCOM * pMBCom )
    : _pMBCom( pMBCom ),
      _hMB   ( NULL )
{
    DBG_ASSERT( _pMBCom );
}

MB::~MB( VOID )
{
    Close();
    _pMBCom = NULL;
}

BOOL MB::EnumObjects( const CHAR * pszPath,
                      CHAR *       Name,
                      DWORD        Index )
{
    HRESULT hRes = _pMBCom->ComMDEnumMetaObjects( _hMB,
                                                  (BYTE *)pszPath,
                                                  (BYTE *)Name,
                                                  Index );

    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }

    SetLastError( HRESULTTOWIN32( hRes ));
    return FALSE;
}

BOOL MB::AddObject( const CHAR * pszPath )
{
    HRESULT hRes = _pMBCom->ComMDAddMetaObject( _hMB,
                                                (BYTE *)pszPath );
    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }

    SetLastError( HRESULTTOWIN32( hRes ));
    return FALSE;
}

BOOL MB::DeleteObject( const CHAR * pszPath )
{
    HRESULT hRes = _pMBCom->ComMDDeleteMetaObject( _hMB,
                                                   (BYTE *)pszPath );

    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }

    SetLastError( HRESULTTOWIN32( hRes ));
    return FALSE;
}

BOOL MB::ReleaseReferenceData( DWORD dwTag )
{
    HRESULT hRes = _pMBCom->ComMDReleaseReferenceData( dwTag );

    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }

    SetLastError( HRESULTTOWIN32( hRes ));
    return FALSE;
}

BOOL MB::Save( VOID )
{
    HRESULT hRes;
    METADATA_HANDLE mdhRoot;


    //
    // First try to lock the tree
    //

    hRes = _pMBCom->ComMDOpenMetaObjectW(METADATA_MASTER_ROOT_HANDLE,
                                            NULL,
                                            METADATA_PERMISSION_READ,
                                            MB_SAVE_TIMEOUT,
                                            &mdhRoot);

    //
    // If failed, then someone has a write handle open,
    // and there might be an inconsistent data state, so don't save.
    //

    if (SUCCEEDED(hRes)) {
        //
        // call metadata com api
        //

        hRes = _pMBCom->ComMDSaveData(mdhRoot);


        _pMBCom->ComMDCloseMetaObject(mdhRoot);

    }

    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }

    SetLastError( HRESULTTOWIN32( hRes ));
    return FALSE;
}

BOOL MB::GetSystemChangeNumber( DWORD *pdwChangeNumber )
{
    HRESULT hRes = _pMBCom->ComMDGetSystemChangeNumber(pdwChangeNumber);

    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }

    SetLastError( HRESULTTOWIN32( hRes ));
    return FALSE;
}

BOOL MB::DeleteData(const CHAR *  pszPath,
                    DWORD         dwPropID,
                    DWORD         dwUserType,
                    DWORD         dwDataType )
{
    HRESULT hRes = _pMBCom->ComMDDeleteMetaData( _hMB,
                                                (LPBYTE) pszPath,
                                                dwPropID,
                                                dwDataType );

    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }
    SetLastError( HRESULTTOWIN32( hRes ));
    return(FALSE);
}

BOOL MB::Close( VOID )
{
    if ( _hMB )
    {
        DBG_REQUIRE( SUCCEEDED(_pMBCom->ComMDCloseMetaObject( _hMB )) );
        _hMB = NULL;
    }

    return TRUE;
}

BOOL
MB::Open(
    METADATA_HANDLE hOpenRoot,
    const CHAR *    pszPath,
    DWORD           dwFlags
    )
/*++

Routine Description:

    Opens the metabase

Arguments:

    hOpenRoot - Relative root or METADATA_MASTER_ROOT_HANDLE
    pszPath - Path to open
    dwFlags - Open flags

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    HRESULT hRes;

    DBG_ASSERT(_hMB == NULL);

    hRes = _pMBCom->ComMDOpenMetaObject( hOpenRoot,
                                         (BYTE *) pszPath,
                                         dwFlags,
                                         MB_TIMEOUT,
                                         &_hMB );

    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }

    DBGPRINTF(( DBG_CONTEXT,
                "[MB::Open] Failed to open %s, error %x (%d)\n",
                pszPath,
                hRes,
                HRESULTTOWIN32( hRes ) ));

    SetLastError( HRESULTTOWIN32( hRes ) );

    return FALSE;
}

BOOL
MB::SetData(
    const CHAR * pszPath,
    DWORD        dwPropID,
    DWORD        dwUserType,
    DWORD        dwDataType,
    VOID *       pvData,
    DWORD        cbData,
    DWORD        dwFlags
    )
/*++

Routine Description:

    Sets a metadata property on an openned metabase

Arguments:

    pszPath - Path to set data on
    dwPropID - Metabase property ID
    dwUserType - User type for this property
    dwDataType - Type of data being set (dword, string etc)
    pvData - Pointer to data
    cbData - Size of data
    dwFlags - Inheritance flags

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    METADATA_RECORD mdRecord;
    HRESULT         hRes;

    DBG_ASSERT( _hMB );

    mdRecord.dwMDIdentifier  = dwPropID;
    mdRecord.dwMDAttributes  = dwFlags;
    mdRecord.dwMDUserType    = dwUserType;
    mdRecord.dwMDDataType    = dwDataType;
    mdRecord.dwMDDataLen     = cbData;
    mdRecord.pbMDData        = (PBYTE) pvData;

    hRes = _pMBCom->ComMDSetMetaData( _hMB,
                                      (LPBYTE) pszPath,
                                      &mdRecord );

    if ( SUCCEEDED( hRes ))
    {
        return TRUE;
    }

    DBGPRINTF(( DBG_CONTEXT,
                "[MB::SetData] Failed to open %s, error %x (%d)\n",
                pszPath,
                hRes,
                HRESULTTOWIN32( hRes ) ));

    SetLastError( HRESULTTOWIN32( hRes ) );

    return FALSE;
}

BOOL
MB::GetData(
    const CHAR *  pszPath,
    DWORD         dwPropID,
    DWORD         dwUserType,
    DWORD         dwDataType,
    VOID *        pvData,
    DWORD *       pcbData,
    DWORD         dwFlags
    )
/*++

Routine Description:

    Retrieves a metadata property on an openned metabase

Arguments:

    pszPath - Path to set data on
    dwPropID - Metabase property ID
    dwUserType - User type for this property
    dwDataType - Type of data being set (dword, string etc)
    pvData - Pointer to data
    pcbData - Size of pvData, receives size of object
    dwFlags - Inheritance flags

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    DBG_ASSERT( _hMB );
    DBG_ASSERT(pcbData);

    METADATA_RECORD mdRecord;
    HRESULT         hRes;
    DWORD           dwRequiredLen = *pcbData;

    mdRecord.dwMDIdentifier  = dwPropID;
    mdRecord.dwMDAttributes  = dwFlags;
    mdRecord.dwMDUserType    = dwUserType;
    mdRecord.dwMDDataType    = dwDataType;
    mdRecord.dwMDDataLen     = *pcbData;
    mdRecord.pbMDData        = (PBYTE) pvData;

    hRes = _pMBCom->ComMDGetMetaData( _hMB,
                                      (LPBYTE) pszPath,
                                      &mdRecord,
                                      &dwRequiredLen );

    if ( SUCCEEDED( hRes ))
    {
        *pcbData = mdRecord.dwMDDataLen;
        return TRUE;
    }

    *pcbData = dwRequiredLen;

#if 0
    DBGPRINTF(( DBG_CONTEXT,
                "[MB::GetData] Failed, PropID(%d), UserType(%d) Flags(%d) on %s, hRes = 0x%08x (%d)\n",
                dwPropID,
                dwUserType,
                dwFlags,
                pszPath,
                hRes,
                HRESULTTOWIN32( hRes ) ));
#endif

    SetLastError( HRESULTTOWIN32( hRes ) );

    return FALSE;
}

BOOL
MB::ReferenceData(
    const CHAR *  pszPath,
    DWORD         dwPropID,
    DWORD         dwUserType,
    DWORD         dwDataType,
    VOID * *      ppvData,
    DWORD *       pcbData,
    DWORD *       pdwTag,
    DWORD         dwFlags
    )
/*++

Routine Description:

    References a metadata property item

Arguments:

    pszPath - Path to set data on
    dwPropID - Metabase property ID
    dwUserType - User type for this property
    dwDataType - Type of data being set (dword, string etc)
    ppvData - Receives pointer to referenced data
    pdwTag - Receives dword tag for releasing this reference
    dwFlags - flags (must have METADATA_REFERENCE)

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    METADATA_RECORD mdRecord;
    HRESULT         hRes;
    DWORD           dwRequiredLen;

    DBG_ASSERT( _hMB );

    mdRecord.dwMDIdentifier  = dwPropID;
    mdRecord.dwMDAttributes  = dwFlags;
    mdRecord.dwMDUserType    = dwUserType;
    mdRecord.dwMDDataType    = dwDataType;
    mdRecord.dwMDDataLen     = 0;
    mdRecord.pbMDData        = NULL;

    hRes = _pMBCom->ComMDGetMetaData( _hMB,
                                      (LPBYTE) pszPath,
                                      &mdRecord,
                                      &dwRequiredLen );

    if ( SUCCEEDED( hRes ))
    {
        *ppvData = mdRecord.pbMDData;
        *pcbData = mdRecord.dwMDDataLen;
        *pdwTag  = mdRecord.dwMDDataTag;

        return TRUE;
    }

    SetLastError( HRESULTTOWIN32( hRes ) );

    return FALSE;
}

BOOL MB::GetAll(
    const CHAR *   pszPath,
    DWORD          dwFlags,
    DWORD          dwUserType,
    BUFFER *       pBuff,
    DWORD *        pcRecords,
    DWORD *        pdwDataSetNumber
    )
/*++

Routine Description:

    Retrieves all the metabase properties on this path of the request type

Arguments:

    pszPath - Path to set data on
    dwFlags - Inerhitance flags
    dwPropID - Metabase property ID
    dwUserType - User type for this property
    dwDataType - Type of data being set (dword, string etc)
    pvData - Pointer to data
    pcbData - Size of pvData, receives size of object
    dwFlags - Inheritance flags

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    DWORD   RequiredSize;
    HRESULT hRes;

    DBG_ASSERT( _hMB );

TryAgain:

    hRes = _pMBCom->ComMDGetAllMetaData( _hMB,
                            (unsigned char *)pszPath,
                            dwFlags,
                            dwUserType,
                            ALL_METADATA,
                            pcRecords,
                            pdwDataSetNumber,
                            pBuff->QuerySize(),
                            (PBYTE)pBuff->QueryPtr(),
                            &RequiredSize
                            );

    // See if we got it, and if we failed because of lack of buffer space
    // try again.

    if ( SUCCEEDED(hRes) )
    {
        return TRUE;
    }

    // Some sort of error, most likely not enough buffer space. Keep
    // trying until we get a non-fatal error.

    if (HRESULT_FACILITY(hRes) == FACILITY_WIN32 &&
        HRESULT_CODE(hRes) == ERROR_INSUFFICIENT_BUFFER) {

        // Not enough buffer space. RequiredSize contains the amount
        // the metabase thinks we need.

        if ( !pBuff->Resize(RequiredSize) ) {

            // Not enough memory to resize.
            return FALSE;
        }

        goto TryAgain;

    }

    return FALSE;
}

BOOL MB::GetDataSetNumber(
    const CHAR *   pszPath,
    DWORD *        pdwDataSetNumber
    )
/*++

Routine Description:

    Retrieves the data set number and size of the data from the
    metabase.

Arguments:

    pszPath - Path to set data on
    pdwDataSetNumber - Where to return the data set number.

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    HRESULT hRes;

    //
    //  We allow _hMB to be null (root handle) for this API (though technically
    //  all the APIs allow the metabase handle to be null)
    //

    hRes = _pMBCom->ComMDGetDataSetNumber( _hMB,
                                           (unsigned char *)pszPath,
                                           pdwDataSetNumber );

    return SUCCEEDED(hRes);
}

BOOL
MB::GetStr(
    const CHAR *  pszPath,
    DWORD         dwPropID,
    DWORD         dwUserType,
    STR *         pstrValue,
    DWORD         dwFlags,
    const CHAR *  pszDefault
    )
/*++

Routine Description:

    Retrieves the string from the metabase.  If the value wasn't found and
    a default is supplied, then the default value is copied to the string.

Arguments:

    pszPath - Path to get data on
    dwPropID - property id to retrieve
    dwUserType - User type for this property
    pstrValue - string that receives the value
    dwFlags - Metabase flags
    pszDefault - Default value to use if the string isn't found, NULL
        for no default value (i.e., will return an error).

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    DWORD cbSize = pstrValue->QuerySize();

TryAgain:

    if ( !GetData( pszPath,
                   dwPropID,
                   dwUserType,
                   STRING_METADATA,
                   pstrValue->QueryStr(),
                   &cbSize,
                   dwFlags ))
    {
        if ( GetLastError() == MD_ERROR_DATA_NOT_FOUND )
        {
            if ( pszDefault != NULL )
            {
                return pstrValue->Copy( pszDefault );
            }

            return FALSE;
        }
        else if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
                  pstrValue->Resize( cbSize ) )
        {
            goto TryAgain;
        }

        return FALSE;
    }

    DBG_REQUIRE( pstrValue->SetLen( cbSize ? (cbSize - 1) : 0  ));

    return TRUE;
}

BOOL
MB::GetMultisz(
    const CHAR *  pszPath,
    DWORD         dwPropID,
    DWORD         dwUserType,
    MULTISZ *     multiszValue,
    DWORD         dwFlags
    )
/*++

Routine Description:

    Retrieves the string from the metabase.  If the value wasn't found and
    a default is supplied, then the default value is copied to the string.

Arguments:

    pszPath - Path to get data on
    dwPropID - property id to retrieve
    dwUserType - User type for this property
    multiszValue - multi-string that receives the value
    dwFlags - Metabase flags

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    DWORD cbSize = multiszValue->QuerySize();

TryAgain:

    if ( !GetData( pszPath,
                   dwPropID,
                   dwUserType,
                   MULTISZ_METADATA,
                   multiszValue->QueryStr(),
                   &cbSize,
                   dwFlags ))
    {
        if ( GetLastError() == MD_ERROR_DATA_NOT_FOUND )
        {
            return FALSE;
        }
        else if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
                  multiszValue->Resize( cbSize ) )
        {
            goto TryAgain;
        }

        return FALSE;
    }

    //
    //  Value was read directly into the buffer so update the member
    //  variables
    //

    multiszValue->RecalcLen();

    return TRUE;
}


BOOL MB::GetDataPaths(
    const CHAR *   pszPath,
    DWORD          dwPropID,
    DWORD          dwDataType,
    BUFFER *       pBuff
    )
/*++

Routine Description:

    Retrieves all the metabase properties on this path of the request type

Arguments:

    pszPath - Path to set data on
    dwFlags - Inerhitance flags
    dwPropID - Metabase property ID
    dwUserType - User type for this property
    dwDataType - Type of data being set (dword, string etc)
    pvData - Pointer to data
    pcbData - Size of pvData, receives size of object
    dwFlags - Inheritance flags

Return:

    TRUE if success, FALSE on error, (call GetLastError())

--*/
{
    DWORD   RequiredSize;
    HRESULT hRes;

    DBG_ASSERT( _hMB );
    DBG_ASSERT( pBuff != NULL );

TryAgain:

    hRes = _pMBCom->ComMDGetMetaDataPaths( _hMB,
                                           (unsigned char *)pszPath,
                                           dwPropID,
                                           dwDataType,
                                           pBuff->QuerySize(),
                                           (PBYTE)pBuff->QueryPtr(),
                                            &RequiredSize
                                           );

    // See if we got it, and if we failed because of lack of buffer space
    // try again.

    if ( SUCCEEDED(hRes) )
    {
        return TRUE;
    }

    // Some sort of error, most likely not enough buffer space. Keep
    // trying until we get a non-fatal error.

    if (HRESULT_FACILITY(hRes) == FACILITY_WIN32 &&
        HRESULT_CODE(hRes) == ERROR_INSUFFICIENT_BUFFER) {

        // Not enough buffer space. RequiredSize contains the amount
        // the metabase thinks we need.

        if ( !pBuff->Resize(RequiredSize) ) {

            // Not enough memory to resize.
            return FALSE;
        }

        goto TryAgain;

    }

    return FALSE;
}