//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 2002.
//
//  File:       cimanger.cxx
//
//  Contents:   The Content Index manager object implementing the
//              ICiManager interface.
//
//  History:    12-03-96   srikants   Created
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <cci.hxx>
#include <prcstob.hxx>
#include <glbconst.hxx>
#include <idxtab.hxx>
#include <enumstr.hxx>

#include "cimanger.hxx"
#include "dmnslave.hxx"

CCiManager::CCiManager( ICiCDocStore * pICiCDocStore )
:_sigCiManger(sigCiManger),
 _refCount(1),
 _state(STARTING),
 _reason(INVALID_REASON),
 _pcci(0),
 _startupFlags(0),
 _fInProc(FALSE),
 _pFilterDaemon(0),
 _pPidLookupTable(0),
 _fNullContentIndex( FALSE )
{
    Win4Assert( pICiCDocStore );

    pICiCDocStore->AddRef();
    _xDocStore.Set( pICiCDocStore );

    ICiCAdviseStatus    *pAdviseStatus = 0;

    SCODE sc = _xDocStore->QueryInterface( IID_ICiCAdviseStatus,
                                           (void **) &pAdviseStatus);
    if ( S_OK != sc )
    {
        Win4Assert( 0 != pAdviseStatus );

        THROW(CException(sc) );
    }

    _xAdviseStatus.Set(pAdviseStatus);

    ICiCLangRes *pICiCLangRes;

    sc = pICiCDocStore->QueryInterface(IID_ICiCLangRes, (void **) &pICiCLangRes);
    if ( FAILED(sc) )
    {
        Win4Assert( !"QI on ICiCLangRes failed" );
        THROW (CException(sc));
    }

    XInterface<ICiCLangRes>  xCiCLangRes(pICiCLangRes);

    _xLangList.Set( new CLangList(pICiCLangRes) );

    CCiAdminParams * pAdminParams = new CCiAdminParams( _xLangList.GetPointer() );
    _xAdminParams.Set( pAdminParams );

    CCiFrameworkParams * pFrameParams = new CCiFrameworkParams( pAdminParams );
    _xFrameParams.Set( pFrameParams );
}

CCiManager::~CCiManager()
{
    Win4Assert( 0 == _refCount );

    delete _pFilterDaemon;
    delete _pcci;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::QueryInterface
//
//  Synopsis:   Returns interfaces to IID_IUknown, IID_ICiManager,
//              IID_ICiStartup, IID_ICiAdmin, IID_ICiFrameworkQuery,
//              IID_ISimpleCommandCreator
//
//  History:    11-27-96   srikants   Created
//               2-14-97   mohamedn   ICiAdmin and ICiFrameworkQuery
//              04-23-97   KrishnaN   Exposed IID_ISimpleCommandCreator
//              04-23-97   KrishnaN   Replaced RtlEqualMemory with ==
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::QueryInterface(
    REFIID riid,
    void **ppvObject)
{
    Win4Assert( 0 != ppvObject );

    if ( IID_ISimpleCommandCreator == riid )
        *ppvObject = (void *)((ISimpleCommandCreator *)this);
    else if ( IID_ICiManager == riid )
        *ppvObject = (void *)((ICiManager *)this);
    else if ( IID_ICiStartup == riid )
        *ppvObject = (void *)((ICiStartup *)this);
    else if ( IID_ICiAdmin == riid )
        *ppvObject = (void *)((ICiAdmin *)this);
    else if ( IID_ICiFrameworkQuery == riid )
        *ppvObject = (void *)((ICiFrameworkQuery *)this);
    else if ( IID_ICiPersistIncrFile == riid )
        *ppvObject = (void *)((ICiPersistIncrFile *)this);
    else if ( (IID_ICiIndexNotification == riid )
              && !_xIndexNotifTable.IsNull() )
    {
        return _xIndexNotifTable->QueryInterface( riid, ppvObject );
    }
    else if ( IID_IUnknown == riid )
        *ppvObject = (void *)((IUnknown *) (ICiManager *) this);
    else
    {
        *ppvObject = 0;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
} //QueryInterface


//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::AddRef
//
//  History:    11-22-96   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CCiManager::AddRef()
{
    return InterlockedIncrement(&_refCount);
} //AddRef

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::Release
//
//  History:    11-22-96   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CCiManager::Release()
{
    Win4Assert( _refCount > 0 );

    LONG refCount = InterlockedDecrement(&_refCount);

    if (  refCount <= 0 )
        delete this;

    return (ULONG) refCount;

}  //Release


//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::GetStatus
//
//  Synopsis:   Retrieves the Content Index status
//
//  Arguments:  [pCiState] -
//
//  History:    1-08-97   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::GetStatus(CIF_STATE *pCiState)
{
    Win4Assert( 0 != pCiState );

    SCODE sc = S_OK;

    if ( 0 != _pcci )
        _pcci->CiState( *pCiState );
    else
        sc = CI_E_NOT_INITIALIZED;

    return sc;

}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::Empty
//
//  Synopsis:   Empties all the contents of the Content Index.
//
//  History:    1-08-97   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::Empty()
{
    SCODE sc = S_OK;

    if ( 0 != _pcci )
        _pcci->Empty();
    else
        sc = CI_E_NOT_INITIALIZED;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::Shutdown
//
//  Synopsis:   Shuts down Content Index. Cannot initiate any more queries after
//              this.
//
//  Returns:    SCODE of the operation.
//
//  History:    12-09-96   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::Shutdown()
{
    SCODE sc = S_OK;

    // =======================================
    {
        CLock   lock(_mutex);
        if ( IsShutdown() )
            return CI_E_SHUTDOWN;
        _state = SHUTDOWN;
    }
    // =======================================

    _saveTracker.SetAbort();

    if ( _pFilterDaemon )
        _pFilterDaemon->InitiateShutdown();

    if ( _pcci )
        _pcci->Dismount();

    //
    // Wait for the death of the filter daemon thread.
    //
    if ( _pFilterDaemon )
        _pFilterDaemon->WaitForDeath();

    _saveTracker.WaitForCompletion();

    //
    // Shutdown buffered notifications in push filtering,
    // _after_ the filter thread has terminated.
    //
    if ( !_xIndexNotifTable.IsNull() )
        _xIndexNotifTable->Shutdown();

    _xPropMapper.Free();
    _xDocStore.Free();

    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::UpdateDocument
//
//  Synopsis:   Schedules a document for update and filtering.
//
//  Arguments:  [pInfo] - Information about the document to be updated.
//
//  History:    12-09-96   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::UpdateDocument(
    const CI_DOCUMENT_UPDATE_INFO * pInfo )
{
    if ( _xIndexNotifTable.IsNull() )
        return UpdDocumentNoThrow( pInfo );
    else
    {
        // Win4Assert( !"UpdateDocument is not available in push filtering" );
        ciDebugOut ((DEB_ERROR,
                     "UpdateDocument is not available in push filtering, wid = 0x%x\n",
                     pInfo->workId ));

        return E_FAIL;
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::StartupNullContentIndex
//
//  Synopsis:   Starts up the NULL content index.
//
//  Arguments:  [pwszCiDirectory] - Starting directory for storing CI data.
//              [pStartupInfo]    - Startup information.
//              [pIProgressNotify]  - Progress notification i/f
//              [pfAbort]           - Ptr to flag controlling abort.
//
//  History:    Jul-09-97   KrishnaN   Created
//
//  Notes:      The thread calling this MUST be in the SYSTEM/ADMINISTRATIVE
//              context, ie, have the highest privilege needed for Content Index.
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::StartupNullContentIndex(
    CI_STARTUP_INFO * pStartupInfo,
    IProgressNotify  *pIProgressNotify,
    BOOL  * pfAbort )
{
    // Anything but null for the second param...
    return StartupContentIndex(TRUE, CINULLCATALOG, pStartupInfo, pIProgressNotify, pfAbort);
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::StartupContentIndex
//
//  Synopsis:   Starts up the content index using the startup parameters
//              provided.
//
//  Arguments:  [pwszCiDirectory] - Starting directory for storing CI data.
//              [pStartupInfo]    - Startup information.
//              [pIProgressNotify]  - Progress notification i/f
//              [pfAbort]           - Ptr to flag controlling abort.
//
//  History:    12-09-96   srikants   Created
//
//  Notes:      The thread calling this MUST be in the SYSTEM/ADMINISTRATIVE
//              context, ie, have the highest privilege needed for Content Index.
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::StartupContentIndex(
    const WCHAR * pwszCiDirectory,
    CI_STARTUP_INFO * pStartupInfo,
    IProgressNotify  *pIProgressNotify,
    BOOL  * pfAbort )
{
    return StartupContentIndex(FALSE, pwszCiDirectory, pStartupInfo, pIProgressNotify, pfAbort);
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::StartupContentIndex
//
//  Synopsis:   Starts up the content index using the startup parameters
//              provided.
//
//  Arguments:  [fNullContentIndex] - Null content index?
//              [pwszCiDirectory]   - Starting directory for storing CI data.
//              [pStartupInfo]      - Startup information.
//              [pIProgressNotify]  - Progress notification i/f
//              [pfAbort]           - Ptr to flag controlling abort.
//
//  History:    12-09-96   srikants   Created
//              02-17-98   kitmanh    Passed fReadOnly into CiStorage
//                                    constructor
//              01-Nov-98  KLam       Passed DiskSpaceToLeave to CiStorage
//
//  Notes:      The thread calling this MUST be in the SYSTEM/ADMINISTRATIVE
//              context, ie, have the highest privilege needed for Content Index.
//
//----------------------------------------------------------------------------

SCODE CCiManager::StartupContentIndex(
    BOOL fNullContentIndex,
    const WCHAR * pwszCiDirectory,
    CI_STARTUP_INFO * pStartupInfo,
    IProgressNotify  *pIProgressNotify,
    BOOL  * pfAbort )
{
    Win4Assert( !CImpersonateSystem::IsImpersonated() );
    Win4Assert( 0 != pwszCiDirectory && 0 != pStartupInfo );

    _fNullContentIndex = fNullContentIndex;

    CLock   lock(_mutex);

    if ( STARTING != _state )
    {
        return CI_E_INVALID_STATE;
    }

    _startupFlags = pStartupInfo->startupFlags;

    BOOL fPushFiltering = FALSE;

    if (!fNullContentIndex)
    {

        _fInProc = pStartupInfo->startupFlags & CI_CONFIG_INPROCESS_FILTERING;
        fPushFiltering = pStartupInfo->startupFlags & CI_CONFIG_PUSH_FILTERING;

        if ( fPushFiltering && !_fInProc )
        {
            Win4Assert( !"Simple filtering is supported only with in process filtering" );

            return E_INVALIDARG;
        }
    }


    SCODE sc = S_OK;

    TRY
    {
        if ( !fNullContentIndex && fPushFiltering )
        {
            XInterface<ICiCDocStore> xDocStore( _xDocStore.GetPointer() );
            xDocStore->AddRef();

            _xIndexNotifTable.Set( new CIndexNotificationTable( this,
                                                                xDocStore ) );
        }

        BOOL fEnableFiltering =
            pStartupInfo->startupFlags & CI_CONFIG_ENABLE_INDEXING;

        // If this is a null content index, filtering should be disabled.
        Win4Assert( (fNullContentIndex && !fEnableFiltering) || !fNullContentIndex);

        if ( 0 == _pcci )
        {
            CLowcaseBuf lcaseDir( pwszCiDirectory );

            //
            // Make a copy to be acquired by CCiManager.
            //
            XArray<WCHAR> xDirCopy( lcaseDir.Length()+1 );

            if (!fNullContentIndex)
            {
                RtlCopyMemory( xDirCopy.GetPointer(), lcaseDir.Get(), xDirCopy.SizeOf() );

                //
                // Create a PStorage object for CI framework data.
                //
                if ( 0 == _xStorage.GetPointer() )
                {
                    BOOL fReadOnly = (pStartupInfo->startupFlags & CI_CONFIG_READONLY) ? TRUE : FALSE;

                    CiStorage * pStorage = new CiStorage( lcaseDir.Get(),
                                                          _xAdviseStatus.GetReference(),
                                                          _xFrameParams->GetMinDiskSpaceToLeave(),
                                                          CURRENT_VERSION_STAMP, 
                                                          fReadOnly );
                    _xStorage.Set( pStorage );
                }
            }
            //
            // Try to obtain a property mapper from the docstore. If successful,
            // we don't have to create one. O/W, we create a property mapper.
            //
            XInterface<IPropertyMapper>  xPropMapper;
            IPropertyMapper * pPropMapper = 0;

            if ( pStartupInfo->startupFlags & CI_CONFIG_PROVIDE_PROPERTY_MAPPER )
            {
                xPropMapper.Set( _LokCreatePropertyMapper() );
            }
            else
            {
                SCODE scPropMapper = _xDocStore->GetPropertyMapper( &pPropMapper );
                if ( !SUCCEEDED(scPropMapper) )
                {
                    ciDebugOut(( DEB_ERROR,
                    "DocStore Is not providing property mapper. Error 0x%X\n",
                    scPropMapper ));
                    THROW( CException( E_INVALIDARG ) );
                }

                xPropMapper.Set( pPropMapper );
            }

            //
            // Update the worker queue registry settings.
            //
            _UpdateQueryWorkerQueueParams();

            //
            // Save the CLSID of the client manager in daemon process.
            //
            _clsidDmnClientMgr = pStartupInfo->clsidDaemonClientMgr;

            if (!fNullContentIndex)
            {
                if ( !_xIndexNotifTable.IsNull() )
                _xIndexNotifTable->AddRef();
                XInterface<CIndexNotificationTable> xNotifTable( _xIndexNotifTable.GetPointer() );

                _pcci = new CCI( _xStorage.GetReference(),
                             _xFrameParams.GetReference(),
                             _xDocStore.GetPointer(),
                             *pStartupInfo,
                             xPropMapper.GetPointer(),
                             xNotifTable );

                _xCiDir.Set( xDirCopy.Count(), xDirCopy.Get() );
                xDirCopy.Acquire();
            }

            _xPropMapper.Set( xPropMapper.Acquire() );


            //
            // Move to the next state depending upon the startup options.
            //
            if ( fEnableFiltering )
            {
                //
                // Check the disk free space situation.
                //
                _state = READY_TO_FILTER;

            }
            else
            {
                _state  = FILTERING_DISABLED;
                _reason = DISABLED_ON_STARTUP;
            }
        }
        else
        {
            sc = CI_E_ALREADY_INITIALIZED;
        }
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();
    }
    END_CATCH


    return sc;

}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::_LokGetStartFilteringError
//
//  Synopsis:   Determines the error code to be returned in the
//              StartFiltering() method based upon the current state.
//
//  Returns:
//
//  History:    12-10-96   srikants   Created
//
//----------------------------------------------------------------------------

SCODE CCiManager::_LokGetStartFilteringError() const
{
    Win4Assert( READY_TO_FILTER != _state );

    if ( FILTERING_DISABLED == _state )
    {
        Win4Assert( INVALID_REASON != _reason );

        if ( DISABLED_ON_STARTUP == _state )
        {
            return CI_E_FILTERING_DISABLED;
        }
        else if ( DISABLED_FOR_DISK_FULL == _reason )
        {
            return CI_E_DISK_FULL;
        }
        else
        {
            return CI_E_INVALID_STATE;
        }
    }
    else if ( FILTERING_ENABLED == _state )
    {
        return S_OK;    // already enabled.
    }
    else
    {
        return CI_E_INVALID_STATE;
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::StartFiltering
//
//  Synopsis:   Start the filtering process
//
//  Arguments:  [cbData] - Count of bytes in startup data
//              [pbData] - Pointer to startup data
//
//  Returns:    CI_E_DISK_FULL if the disk is full and so filtering cannot be
//              started.
//
//              CI_E_FILTERING_DISABLED if filtering was disabled in the startup
//              options.
//
//              CI_E_INVALID_STATE if this is not a valid call in this state.
//              Usually signifies that shutdown is in progress.
//
//  History:    12-09-96   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::StartFiltering(
    ULONG cbData,
    BYTE const * pbData )
{
    SCODE sc = S_OK;

    // ==============================================================
    {
        CLock   lock(_mutex);

        if ( READY_TO_FILTER != _state )
        {
            return _LokGetStartFilteringError();
        }

        TRY
        {
            if ( 0 == _pFilterDaemon )
            {
                CSharedNameGen  nameGen( _xCiDir.Get() );

                _pFilterDaemon = new CDaemonSlave( *this,
                                               _pcci,
                                               _xCiDir.Get(),
                                               nameGen,
                                               _clsidDmnClientMgr );
            }

            _pFilterDaemon->StartFiltering( pbData, cbData);

            _state = FILTERING_ENABLED;
        }
        CATCH( CException, e )
        {
            sc = e.GetErrorCode();
        }
        END_CATCH
    }
    // ==============================================================

    return sc;
}



//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::FlushUpdates
//
//  Synopsis:   Flushes all update notifications to disk
//
//  Returns:    S_OK if successful; error code otherwise.
//
//  History:    27-Jun-97    SitaramR   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::FlushUpdates()
{
    SCODE sc = S_OK;

    TRY
    {
        if ( IsShutdown() )
            sc = CI_E_SHUTDOWN;
        else
            _pcci->FlushUpdates();
    }
    CATCH( CException, e )
    {
        sc = e.GetErrorCode();

        ciDebugOut(( DEB_ERROR,
                     "FlushUpdates - caught exception 0x%x\n",
                     sc ));
    }
    END_CATCH

    return sc;
}



//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::ForceMerge
//
//  Synopsis:   Forces a master merge on the content index.
//
//  Arguments:  [mt] -- Merge type (shadow, master, etc.)
//
//  Returns:    S_OK if successfully forced; error code otherwise.
//
//  History:    12-11-96   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::ForceMerge( CI_MERGE_TYPE mt )
{
    SCODE sc = S_OK;

    if ( 0 != _pcci )
    {
        Win4Assert( STARTING != _state );
        sc = _pcci->ForceMerge( partidDefault, mt );
    }
    else
    {
        sc = CI_E_NOT_INITIALIZED;
    }

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::AbortMerge
//
//  Synopsis:   Aborts any in progress merge on the content index.
//
//  Returns:    S_OK if successfully forced; error code otherwise.
//
//  History:    12-11-96   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::AbortMerge()
{
    SCODE sc = S_OK;

    if ( 0 != _pcci )
    {
        Win4Assert( STARTING != _state );
        sc = _pcci->AbortMerge( partidDefault );
    }
    else
    {
        sc = CI_E_NOT_INITIALIZED;
    }

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::IsQuiesced
//
//  Synopsis:   Tests if Content Index is quiesced (no activity). ContentIndex
//              is considered quiesced if there are no outstanding documents
//              to filter, there is only one index and there are no wordlists.
//
//  Arguments:  [pfState] -  Set to TRUE if it CI is quiesced; FALSE o/w
//
//  History:    1-08-97   srikants   Created
//
//----------------------------------------------------------------------------


STDMETHODIMP CCiManager::IsQuiesced( BOOL * pfState )
{

    Win4Assert( 0 != pfState );

    CIF_STATE ciState;
    ciState.cbStruct = sizeof ciState;

    SCODE sc =  GetStatus( &ciState );
    if ( S_OK == sc )
    {
        *pfState = 0 == ciState.eState &&
                   0 == ciState.cWordList &&
                   0 == ciState.cDocuments &&
                   0 == ciState.cFreshTest &&
                   ciState.cPersistentIndex <= 1;
    }

    return sc;
}

//
// ICiPersistIncrFile methods
//
//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::Save
//
//  Synopsis:   Saves the Content Index data in the directory provided.
//              An incremental save for the first time will actually be a full
//              save.
//
//  Arguments:  [pwszSaveDirectory] - The directory in which to save the backup
//              data. This directory MUST be on a local drive and security protected.
//              It is recommended that this directory has the same security set as the
//              current content index directory. Also, it MUST be on a local drive.
//              [fFull]             - Set to TRUE if a full save is being requested.
//              [pIProgressNotify]  - Interface to give progress notification.
//              [pfAbort]           - This flag will be set to TRUE by the caller if
//              the caller wants the operation to be aborted before completion.
//              [ppWorkidList]      - On output, will have the list of changed workids.
//              On a full save, this list may be empty in which case the caller must
//              assume that all the know workids have changed.
//              [ppFileList]        - On output, will be set to the filelist enumerator.
//              [pfFull]            - Set to TRUE if a FULL save was done.
//              [pfCallerOwnsFiles] - Set to TRUE if the caller has the responsibility to
//              clean up the files.
//
//  Returns:    S_OK if successful
//              Other error code as appropriate.
//
//  History:    3-20-97   srikants   Created
//              01-Nov-98 KLam       Added DiskSpaceToLeave to CiStorage constructor
//
//  Notes:      Handle DISK_FULL as a special case.
//              Right now there is no restartability - if an operation fails in
//              the middle due to any problem, the whole operation is aborted
//              and the partially created files are deleted. We should do some
//              logging to indicate the progress made so far and have a notion of
//              restartability. The time I have does not permit doing the
//              restartability - SriKants - 3-20-97
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::Save(
                    const WCHAR *pwszSaveDirectory,
                    BOOL fFull,
                    IProgressNotify  *pIProgressNotify,
                    BOOL  *pfAbort,
                    ICiEnumWorkids  ** ppWorkidList,
                    IEnumString ** ppFileList,
                    BOOL * pfFull,
                    BOOL * pfCallerOwnsFiles)

{

    Win4Assert( !CImpersonateSystem::IsImpersonated() );

    if ( !_IsIncrIndexingEnabled() )
        return CI_E_INVALID_STATE;

    if ( 0 == _pcci )
        return CI_E_INVALID_STATE;

    if ( IsShutdown() )
        return CI_E_SHUTDOWN;

    //=====================================
    {
        //
        // There can be only one instance of "Save()" running at
        // any time.
        //
        CLock   lock(_mutex);
        if ( !_saveTracker.LokIsTracking() )
        {
            _saveTracker.LokStartTracking( pIProgressNotify, pfAbort );
        }
        else
        {
            return CI_E_INVALID_STATE;
        }
    }
    //=====================================

    SCODE sc = S_OK;

    TRY
    {

        if ( !_IsValidSaveDirectory( pwszSaveDirectory ) )
        {
            ciDebugOut(( DEB_ERROR, "Invalid Save Directory (%ws)\n",
                                    pwszSaveDirectory ));
            THROW( CException( E_INVALIDARG ) );
        }

        XInterface<ICiEnumWorkids>  xEnumWorkids;

        XPtr<CiStorage> xStorage(
            new CiStorage( pwszSaveDirectory,
                          _xAdviseStatus.GetReference(),
                          _xFrameParams->GetMinDiskSpaceToLeave(),
                          CURRENT_VERSION_STAMP ));

        //
        // Until we have a restartable save, nuke all the files in the specified
        // directory.
        //
        xStorage->DeleteAllFiles();

        BOOL fIsFull = fFull;
        SCODE sc = _pcci->BackupCiData( xStorage.GetReference(),
                                        fIsFull,                 // in-out parameter
                                        xEnumWorkids,
                                        _saveTracker );

        if ( !SUCCEEDED(sc) )
        {
            THROW( CException(sc) );
        }

        //
        // If we provided the property mapper, we must copy that too.
        //
        if ( 0 != _pPidLookupTable )
            _CreateBackupOfPidTable( xStorage.GetReference(), _saveTracker );

        //
        // Create an enumerated list of the files created in this save.
        //
        CEnumString * pEnumStr = new CEnumString;
        XInterface<IEnumString> xEnumStr( pEnumStr );
        CiStorage::EnumerateFilesInDir( pwszSaveDirectory, *pEnumStr );

        //
        // Setup the return parameter values.
        //
        *ppWorkidList = xEnumWorkids.Acquire();
        *ppFileList = xEnumStr.Acquire();
        *pfFull = fIsFull;
        *pfCallerOwnsFiles = TRUE;
    }
    CATCH( CException,e )
    {
        ciDebugOut(( DEB_ERROR,
                     "Error in CCiManager::Save. (0x%X)\n",
                     e.GetErrorCode() ));

        sc = e.GetErrorCode();
    }
    END_CATCH

    //=====================================
    {
        CLock   lock(_mutex);
        _saveTracker.LokStopTracking();
    }
    //=====================================

    return sc;

}


// ICiFrameWorkQuery interface - interface that is internal to the
// framework indexing and querying only.
//

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::ProcessError
//
//  Synopsis:   Processes the error code. Only the corruption error is of
//              interest to notify the content index which in turn will
//              notify the client.
//
//  Arguments:  [lErrorCode] -
//
//  History:    1-08-97   srikants   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::ProcessError( long lErrorCode )
{
    if ( IsCiCorruptStatus( lErrorCode ) && 0 != _pcci )
        _pcci->MarkCorruptIndex();

    return S_OK;
}

//
// ISimpleCommandCreator methods
//

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::CreateICommand, public
//
//  Synopsis:   Exposes an ICommand from the framework.
//
//  Arguments:  [ppiCommand] - Transports back an instance of the ICommand
//
//  History:    04-22-97   KrishnaN   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CCiManager::CreateICommand (IUnknown ** ppIUnknown, IUnknown * pOuterUnk)
{
    if (0 == ppIUnknown)
    {
        ciDebugOut((DEB_ERROR, "Invalid ppICommand passed to CCiManager::CreateICommand"));
        return E_INVALIDARG;
    }

    *ppIUnknown = 0;

    if ( 0 ==_xDocStore.GetPointer() )
        return E_NOINTERFACE;

    return MakeLocalICommand(ppIUnknown, _xDocStore.GetPointer(), pOuterUnk);
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::VerifyCatalog, public
//
//  Synopsis:   Validate catalog location
//
//  Arguments:  [pwszMachine]     -- Machine on which catalog exists
//              [pwszCatalogName] -- Catalog Name
//
//  Returns:    S_OK for now...
//
//  History:    22-Jul-97   KyleP   Created
//
//----------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CCiManager::VerifyCatalog( WCHAR const * pwszMachine,
                                                   WCHAR const * pwszCatalogName )
{
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::GetDefaultCatalog, public
//
//  Synopsis:   Determine 'default' catalog for system
//
//  Arguments:  [pwszCatalogName] -- Catalog Name
//              [cwcIn]           -- Size in characters of [pwszCatalogName]
//              [pcwcOut]         -- Size of catalog name
//
//  Returns:    E_NOTIMPL
//
//  History:    22-Jul-97   KyleP   Created
//
//----------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CCiManager::GetDefaultCatalog( WCHAR * pwszCatalogName,
                                                       ULONG cwcIn, ULONG * pcwcOut )
{
    return E_NOTIMPL;
}

//
// Non-Interface methods
//

void CCiManager::ProcessCiDaemonTermination( SCODE sc )
{
    CLock   lock(_mutex);

    if ( _xDocStore.GetPointer() )
    {
        _state = READY_TO_FILTER;
        sc = _xDocStore->ProcessCiDaemonTermination(sc);
        if ( !SUCCEEDED(sc) )
        {
            ciDebugOut(( DEB_ERROR,
                         "ICiCDocStore::ProcessCiDaemonTermination returned 0x%X\n",
                         sc ));
            THROW( CException( sc ) );
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::_LokCreatePropertyMapper
//
//  Synopsis:   Creates a property mapper object.
//
//  History:    1-31-97   srikants   Created
//
//----------------------------------------------------------------------------

IPropertyMapper * CCiManager::_LokCreatePropertyMapper()
{
    Win4Assert( 0 == _xPropMapper.GetPointer() );
    Win4Assert( 0 == _pPidLookupTable );

    XPtr<CPidLookupTable> xPidTable( new CPidLookupTable );

    PRcovStorageObj * pObj = _xStorage->QueryPidLookupTable(0);

    // Init takes ownership regardless of whether it succeeds.

    if ( !xPidTable->Init( pObj ) )
    {
        ciDebugOut ((DEB_ERROR, "Failed init of PidTable\n"));
        THROW (CException(CI_CORRUPT_CATALOG));
    }

    IPropertyMapper * pMapper = new CFwPropertyMapper( xPidTable.GetReference(),
                                                       _fNullContentIndex, // map std props only?
                                                       TRUE // Own the pid table
                                                     );
    _pPidLookupTable = xPidTable.Acquire();

    return pMapper;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::_CreateBackupOfPidTable
//
//  Synopsis:   Creates a copy of the pid table.
//
//  Arguments:  [storage] - Storage to use
//              [tracker] - Progress Tracker
//
//  History:    3-20-97   srikants   Created
//
//----------------------------------------------------------------------------

void CCiManager::_CreateBackupOfPidTable( CiStorage & storage,
                                          PSaveProgressTracker & tracker )
{
    Win4Assert( 0 != _pPidLookupTable );

    PRcovStorageObj * pObj = storage.QueryPidLookupTable(0);
    XPtr<PRcovStorageObj> xObj(pObj);

    _pPidLookupTable->MakeBackupCopy( *pObj, tracker );

}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::_UpdateQueryWorkerQueueParams
//
//  Synopsis:   Updates the worker queue parameters
//
//  History:    1-03-97   srikants   Created
//
//----------------------------------------------------------------------------

void CCiManager::_UpdateQueryWorkerQueueParams()
{
    //
    // Getting these from the registry is fine for Indexing Service,
    // but will be fixed in the PKM codebase.
    //

    ULONG cMaxActiveThreads, cMinIdleThreads;
    TheWorkQueue.GetWorkQueueRegParams( cMaxActiveThreads,
                                        cMinIdleThreads );
    TheWorkQueue.RefreshParams( cMaxActiveThreads, cMinIdleThreads );
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::UpdDocumentNoThrow
//
//  Synopsis:   Real worker function that schedules a document for update
//              and filtering. This is called directly in push filtering.
//
//  Arguments:  [pInfo] - Information about the document to be updated.
//
//  History:    15-Mar-97     SitaramR      Created
//
//  Notes:      Does not throw because this is simply a worker function
//              for UpdateDocument
//
//----------------------------------------------------------------------------

SCODE CCiManager::UpdDocumentNoThrow( const CI_DOCUMENT_UPDATE_INFO * pInfo )
{
    Win4Assert( 0 != pInfo );

    SCODE sc = S_OK;

    TRY
    {
        if ( 0 == _pcci  )
            sc = CI_E_NOT_INITIALIZED;
        else
        {
            ULONG hint = _pcci->ReserveUpdate( pInfo->workId );

            ULONG action = CI_UPDATE_OBJ;
            if ( CI_UPDATE_DELETE == pInfo->change )
                action = CI_DELETE_OBJ;

            sc = _pcci->Update( hint,
                                pInfo->workId,
                                partidDefault,
                                pInfo->usn,
                                pInfo->volumeId,
                                action );
        }
    }
    CATCH( CException,e )
    {
        ciDebugOut(( DEB_ERROR,
                     "CCiManager::UpdDocument - Error 0x%X\n",
                     e.GetErrorCode() ));
        sc = e.GetErrorCode();
    }
    END_CATCH

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::_IsValidLoadDirectory
//
//  Synopsis:   Verifies that the given directory is a valid directory.
//              It ensures that files INDEX.000, INDEX.001, INDEX.002
//              are in the directory.
//
//  Arguments:  [pwszDirPath] - Directory path to verify.
//
//  Returns:    TRUE if valid; FALSE o/w; May throw exceptions.
//
//  History:    3-21-97   srikants   Created
//
//----------------------------------------------------------------------------

BOOL CCiManager::_IsValidSaveDirectory( WCHAR const * pwszDirPath )
{
    //
    // Confirm that it is a local drive.
    //
    UINT driveType = CiStorage::DetermineDriveType( pwszDirPath );
    if ( DRIVE_FIXED != driveType )
    {
        ciDebugOut(( DEB_ERROR,
                     "The given path (%ws) is not a local fixed disk\n",
                     pwszDirPath ));
        THROW( CException( E_INVALIDARG ) );
    }

    const MAX_DIR_PATH = MAX_PATH - 13; // Leave room for "\\8.3"


    DWORD dwFileAttributes = GetFileAttributes( pwszDirPath );
    if ( 0xFFFFFFFF == dwFileAttributes )
        return FALSE;

    return dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiManager::_CompareIncrDataSequence
//
//  Synopsis:   Compares the sequence number information stored in the
//              current catalog and the information in the new files.
//
//  Arguments:  [pwszNewFilesDir] - Directory where the new INDEX.* files
//              are located.
//
//  Returns:    TRUE if they match. FALSE o/w
//
//  History:    3-19-97   srikants   Created
//              01-Nov-98 KLam       Added DiskSpaceToLeave to CiStorage constructor
//
//----------------------------------------------------------------------------

BOOL CCiManager::_CompareIncrDataSequence( WCHAR const * pwszNewFilesDir )
{
    Win4Assert( !_xStorage.IsNull() );

    //
    // Create a new CiStorage for the target directory.
    //
    XPtr<CiStorage>   xStorage(
            new CiStorage( pwszNewFilesDir,
                           _xAdviseStatus.GetReference(),
                           _xFrameParams->GetMinDiskSpaceToLeave(),
                           CURRENT_VERSION_STAMP) );

    //
    // Create the index table for the current storage location.
    //
    CTransaction    xact;
    XPtr<CIndexTable> xCurrTable(
                        new CIndexTable( _xStorage.GetReference(), xact ));

    XPtr<CIndexTable> xNewTable(
                        new CIndexTable( xStorage.GetReference(), xact ));

    unsigned currVersion, newVersion;
    BOOL     fFullSave;

    xCurrTable->GetUserHdrInfo( currVersion, fFullSave ); // fFullSave is ignored
    xNewTable->GetUserHdrInfo( newVersion, fFullSave );

    ciDebugOut(( DEB_WARN, "%s\nCurrent Version = %d\n New Version = %d\n",
                  fFullSave ? "FullSave" : "Incr. Save",
                  currVersion, newVersion ));

    if ( fFullSave )
    {
        //
        // For FULL save, any version is fine.
        return TRUE;
    }
    else
    {
        //
        // For an incremental save, the seqnums in the source and
        // destination must match. Otherwise, it should be ignored.
        //
        return currVersion == newVersion;
    }

}