//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1999 - 1999
//
//  File:       doccnfg.cpp
//
//--------------------------------------------------------------------------


#include "stdafx.h"
#include "doccnfg.h"
#include "comdbg.h"

/////////////////////////////////////////////////////////////////////////////
//
// external references
extern const wchar_t* AMCSnapInCacheStreamName;

/////////////////////////////////////////////////////////////////////////////
//
// Class CMMCDocConfig implementation

CMMCDocConfig::~CMMCDocConfig()
{
    if (IsFileOpen())
        CloseFile();
}


STDMETHODIMP CMMCDocConfig::InterfaceSupportsErrorInfo(REFIID riid)
{
    return (InlineIsEqualGUID(IID_IDocConfig, riid)) ? S_OK : S_FALSE;
}


STDMETHODIMP CMMCDocConfig::OpenFile(BSTR bstrFilePath)
{
    return ScOpenFile( bstrFilePath ).ToHr();
}


STDMETHODIMP CMMCDocConfig::CloseFile()
{
    return ScCloseFile().ToHr();
}


STDMETHODIMP CMMCDocConfig::SaveFile(BSTR bstrFilePath)
{
    return ScSaveFile(bstrFilePath).ToHr();
}


STDMETHODIMP CMMCDocConfig::EnableSnapInExtension(BSTR bstrSnapIn, BSTR bstrExt, VARIANT_BOOL bEnable)
{
    return ScEnableSnapInExtension(bstrSnapIn, bstrExt, bEnable).ToHr();
}


/*+-------------------------------------------------------------------------*
 * CMMCDocConfig::Dump
 *
 *
 *--------------------------------------------------------------------------*/

STDMETHODIMP CMMCDocConfig::Dump (LPCTSTR pszDumpFilePath)
{
    return ScDump (pszDumpFilePath).ToHr();
}


/*+-------------------------------------------------------------------------*
 * CMMCDocConfig::CheckSnapinAvailability
 *
 *
 *--------------------------------------------------------------------------*/

STDMETHODIMP CMMCDocConfig::CheckSnapinAvailability (CAvailableSnapinInfo& asi)
{
    return ScCheckSnapinAvailability(asi).ToHr();
}

/***************************************************************************\
 *
 * METHOD:  CMMCDocConfig::ScOpenFile
 *
 * PURPOSE: Opens the specified console file and reads snapin cache from it
 *
 * PARAMETERS:
 *    BSTR bstrFilePath [in] file name to read from
 *
 * RETURNS:
 *    SC    - result code
 *
\***************************************************************************/
SC CMMCDocConfig::ScOpenFile(BSTR bstrFilePath)
{
    DECLARE_SC(sc, TEXT("CMMCDocConfig::ScOpenFile"));

    // Close currently open file
    if (IsFileOpen())
    {
        sc = ScCloseFile();
        if (sc)
            sc.TraceAndClear(); // report the error and ignore
    }

    // parameter check
    if (bstrFilePath == NULL || SysStringLen(bstrFilePath) == 0)
        return sc = E_INVALIDARG;

    USES_CONVERSION;
    LPCTSTR lpstrFilePath = OLE2CT(bstrFilePath);

    // create object to load the snapins
    CAutoPtr<CSnapInsCache> spSnapInsCache( new CSnapInsCache );
    sc = ScCheckPointers( spSnapInsCache, E_OUTOFMEMORY );
    if (sc)
        return sc;

    // load the data (use bas class method)
    bool bXmlBased = false;
    CXMLDocument xmlDocument;
    IStoragePtr spStorage;
    sc = ScLoadConsole( lpstrFilePath, bXmlBased, xmlDocument, &spStorage );
    if (sc)
        return sc;

    // examine file type
    if ( !bXmlBased )
    {
        // structured storage - based console
        IStreamPtr spStream;
        sc = OpenDebugStream(spStorage, AMCSnapInCacheStreamName,
                         STGM_SHARE_EXCLUSIVE | STGM_READWRITE, L"SnapInCache", &spStream);
        if (sc)
            return sc;

        sc = spSnapInsCache->ScLoad(spStream);
        if (sc)
            return sc;

        m_spStorage = spStorage;
    }
    else
    {
        // xml - based console

        try // xml implementation throws sc's
        {
            // construct parent document
            CXMLElement elemDoc = xmlDocument;
            CPersistor persistorFile(xmlDocument, elemDoc);
            // init
            persistorFile.SetLoading(true);

            // navigate to snapin cache
            CPersistor persistorConsole ( persistorFile,    XML_TAG_MMC_CONSOLE_FILE );
            CPersistor persistorTree    ( persistorConsole, XML_TAG_SCOPE_TREE );

            // load
            persistorTree.Persist(*spSnapInsCache);

            // hold onto the persistor info
            m_XMLDocument = persistorConsole.GetDocument();
            m_XMLElemConsole = persistorConsole.GetCurrentElement();
            m_XMLElemTree = persistorTree.GetCurrentElement();
        }
        catch(SC& sc_thrown)
        {
            return (sc = sc_thrown);
        }
    }

    // keep on the pointer
    m_spCache.Attach( spSnapInsCache.Detach() );
    m_strFilePath = lpstrFilePath;

    return sc;
}

/***************************************************************************\
 *
 * METHOD:  CMMCDocConfig::ScCloseFile
 *
 * PURPOSE: closes open file
 *
 * PARAMETERS:
 *
 * RETURNS:
 *    SC    - result code
 *
\***************************************************************************/
SC CMMCDocConfig::ScCloseFile()
{
    DECLARE_SC(sc, TEXT("CMMCDocConfig::ScCloseFile"));

    if (!IsFileOpen())
        return sc = E_UNEXPECTED;

    // release everything
    m_spStorage = NULL;
    m_strFilePath.erase();
    m_spCache.Delete();
    m_XMLDocument = CXMLDocument();
    m_XMLElemConsole = CXMLElement();
    m_XMLElemTree = CXMLElement();

    return sc;
}

/***************************************************************************\
 *
 * METHOD:  ScFindAndTruncateChild
 *
 * PURPOSE: helper; locates the element by tag and removes all element's contents
 *          Doing so instead of deleting and recreating the element preserves all
 *          the formating and tag order in xml document
 *
 * PARAMETERS:
 *    CPersistor& parent    [in] - parent persistor
 *    LPCTSTR strTag        [in] - child's tag
 *    CXMLElement& child    [out] - child's element
 *
 * RETURNS:
 *    SC    - result code
 *
\***************************************************************************/
SC ScFindAndTruncateChild(CPersistor& parent, LPCTSTR strTag, CXMLElement& child)
{
    DECLARE_SC(sc, TEXT("ScTruncateChild"));

    try
    {
        // create persistor for the old cache tag
        parent.SetLoading(true); // we want 'loading-alike' navigation
        CPersistor persistorChild( parent, strTag );
        parent.SetLoading(false); // restore saving behavior

        // get the element
        CXMLElement elChild = persistorChild.GetCurrentElement();

        // get nodes under the element
        CXMLElementCollection colChildren;
        elChild.get_children( &colChildren );

        long count = 0;
        colChildren.get_count( &count );

        // iterate and delete all the nodes
        while (count > 0)
        {
            CXMLElement el;
            colChildren.item( 0, &el);

            elChild.removeChild(el);

            --count;
        }

        // return the element
        child = elChild;
    }
    catch(SC& sc_thrown)
    {
        return (sc = sc_thrown);
    }

    return sc;
}

/***************************************************************************\
 *
 * METHOD:  CMMCDocConfig::ScSaveFile
 *
 * PURPOSE: Saves file to specified location
 *
 * PARAMETERS:
 *    BSTR bstrFilePath [in] file path to save to. NULL -> same as load
 *
 * RETURNS:
 *    SC    - result code
 *
\***************************************************************************/
SC CMMCDocConfig::ScSaveFile(BSTR bstrFilePath)
{
    DECLARE_SC(sc, TEXT("CMMCDocConfig::ScSaveFile"));

    if (!IsFileOpen() || m_spCache == NULL)
        return sc = E_UNEXPECTED;

    USES_CONVERSION;

    // if new path specified, save local copy as new default
    if ( bstrFilePath && SysStringLen(bstrFilePath) != 0)
        m_strFilePath = OLE2CT(bstrFilePath);

    // remove extensions marked for deletion
    m_spCache->Purge(TRUE);

    if ( m_spStorage != NULL ) // not the XML way?
    {
        // replace snapin cache stream with new cache contents
        IStreamPtr spStream;
        sc = CreateDebugStream(m_spStorage, AMCSnapInCacheStreamName,
                STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_READWRITE, L"SnapInCache", &spStream);
        if (sc)
            return sc;

        // save the cache
        sc = m_spCache->ScSave(spStream, TRUE);
        if (sc)
            return sc;

        // Create storage for the requested file
        IStoragePtr spNewStorage;
        sc = CreateDebugDocfile( T2COLE( m_strFilePath.c_str() ),
            STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_CREATE | STGM_READWRITE,
            &spNewStorage);

        if (sc)
            return sc;

        // copy the working storage to the new file
        sc = m_spStorage->CopyTo(NULL, NULL, NULL, spNewStorage);
        if (sc)
            return sc;

        // lets hold on the new one
        m_spStorage = spNewStorage;
    }
    else
    {
        try // may throw
        {
            // save the data

            CPersistor persistorTree( m_XMLDocument, m_XMLElemTree );

            // this is more tricky than loading - we want to reuse the same tag

            CXMLElement elCache;
            sc = ScFindAndTruncateChild(persistorTree, m_spCache->GetXMLType(), elCache);
            if (sc)
                return sc;

            // create persistor for the new cache tag
            CPersistor persistorCache( persistorTree, elCache );

            // now persist under the new tag
            m_spCache->Persist(persistorCache);

            // update documents guid to invalidate user data

            GUID  guidConsoleId;
            sc = CoCreateGuid(&guidConsoleId);
            if (sc)
                return sc;

            // persistor for console
            CPersistor persistorConsole ( m_XMLDocument,  m_XMLElemConsole );
            persistorConsole.SetLoading(false);

            CXMLElement elGuid;
            sc = ScFindAndTruncateChild(persistorConsole, XML_TAG_CONSOLE_FILE_UID, elGuid);
            if (sc)
                return sc;

            // create persistor for the new guid tag
            CPersistor persistorGuid( persistorConsole, elGuid );

            // now persist under the new tag
            persistorGuid.PersistContents(guidConsoleId);

            //save to file
            sc = ScSaveConsole( m_strFilePath.c_str(), true/*bForAuthorMode*/, m_XMLDocument);
            if (sc)
                return sc;
        }
        catch(SC& sc_thrown)
        {
            return (sc = sc_thrown);
        }
    }

    return sc;
}

/***************************************************************************\
 *
 * METHOD:  CMMCDocConfig::ScEnableSnapInExtension
 *
 * PURPOSE: Enables extension in snapin cache
 *
 * PARAMETERS:
 *    BSTR bstrSnapIn       [in] classid of the snapin
 *    BSTR bstrExt          [in] classid of extension
 *    VARIANT_BOOL bEnable  [in] enable/disable flag
 *
 * RETURNS:
 *    SC    - result code
 *
\***************************************************************************/
SC CMMCDocConfig::ScEnableSnapInExtension(BSTR bstrSnapIn, BSTR bstrExt, VARIANT_BOOL bEnable)
{
    DECLARE_SC(sc, TEXT("CMMCDocConfig::ScEnableSnapInExtension"));

    CLSID SnapInCLSID;
    CLSID ExtCLSID;
    CSnapInPtr spBaseSnapIn;
    CSnapInPtr spExtSnapIn;

    // convert input strings to CLSIDs
    sc = CLSIDFromString(bstrSnapIn, &SnapInCLSID);
    if (sc)
        return sc;

    sc = CLSIDFromString( bstrExt, &ExtCLSID);
    if (sc)
        return sc;

    // Locate base snap-in in cache
    sc = m_spCache->ScFindSnapIn(SnapInCLSID, &spBaseSnapIn);
    if (sc)
        return sc = E_INVALIDARG;

    // Check if extension is enabled
    CExtSI* pExt = spBaseSnapIn->GetExtensionSnapIn();
    while (pExt != NULL)
    {
        if (pExt->GetSnapIn()->GetSnapInCLSID() == ExtCLSID)
            break;

        pExt = pExt->Next();
    }

    // if extension is present and not marked for deletion
    if (pExt != NULL && !pExt->IsMarkedForDeletion())
    {
        // If should be disabled, just mark deleted
        if (!bEnable)
            pExt->MarkDeleted(TRUE);
    }
    else
    {
        // if should be enabled
        if (bEnable)
        {
            // if extension is present, just undelete
            if (pExt != NULL)
            {
                pExt->MarkDeleted(FALSE);
            }
            else
            {
                // Find or create cache entry for extension snapin
                sc = m_spCache->ScGetSnapIn(ExtCLSID, &spExtSnapIn);
                if (sc)
                    return sc;

                // Add as extension to base snapin
                spBaseSnapIn->AddExtension(spExtSnapIn);
            }
        }
    }

    return sc;
}



/***************************************************************************\
 *
 * METHOD:  CMMCDocConfig::ScDump
 *
 * PURPOSE: dumps contents of snapin cache
 *
 * PARAMETERS:
 *    LPCTSTR pszDumpFilePath [in] file to dump to
 *
 * RETURNS:
 *    SC    - result code
 *
\***************************************************************************/
SC CMMCDocConfig::ScDump (LPCTSTR pszDumpFilePath)
{
    DECLARE_SC(sc, TEXT("CMMCDocConfig::ScDump"));

	/*
	 * validate input
	 */
	sc = ScCheckPointers (pszDumpFilePath);
	if (sc)
		return sc;

    if (pszDumpFilePath[0] == 0)
        return sc = E_INVALIDARG;

	/*
	 * make sure a file is open
	 */
	if (!IsFileOpen())
		return ((sc = E_UNEXPECTED).ToHr());

	sc = ScCheckPointers (m_spCache, E_UNEXPECTED);
	if (sc)
		return (sc.ToHr());

    return (m_spCache->Dump (pszDumpFilePath));
}



/***************************************************************************\
 *
 * METHOD:  CMMCDocConfig::ScCheckSnapinAvailability
 *
 * PURPOSE:
 *
 * PARAMETERS:
 *    	BOOL  f32bit            [in]    // check 32-bit (vs. 64-bit) snap-ins?
 *    	UINT& cTotalSnapins     [out]   // total number of snap-ins referenced in the console file
 *    	UINT& cAvailableSnapins [out]   // number of snap-ins available in the requested memory model
 *
 * RETURNS:
 *    SC    - result code
 *
\***************************************************************************/
SC CMMCDocConfig::ScCheckSnapinAvailability (CAvailableSnapinInfo& asi)
{
    DECLARE_SC(sc, TEXT("CMMCDocConfig::ScCheckSnapinAvailability"));

	/*
	 * make sure a file is open
	 */
	if (!IsFileOpen())
		return ((sc = E_UNEXPECTED).ToHr());

	sc = ScCheckPointers (m_spCache, E_UNEXPECTED);
	if (sc)
		return sc;

	sc = m_spCache->ScCheckSnapinAvailability (asi);
	if (sc)
		return sc;

	return sc;
}