//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1992 - 2000.
//
//  File:       scanmgr.cxx
//
//  Contents:   Scan manager
//
//  History:    14-Jul-97   SitaramR  Created from dlnotify.cxx
//
//  Notes  :    For lock hierarchy and order of acquiring locks, please see
//              cicat.cxx
//
//  DISKFULL HANDLING
//
//  The disk full situation is either detected in RESMAN and information sent
//  up to CICAT or a DISKFULL error is first detected in CICAT and then
//  propagated to RESMAN. As part of diskfull processing in the scope table,
//  existing scans are aborted in scanmanager and future scans are disabled
//  until the diskfull gets cleared up. If DISKFULL is detected at startup
//  time, the scope table enters a "incremental scan required" state and doesn't
//  schedule any scans/notifications until the situation improves.
//
//  If the changelog loses a notification, a DisableUpdates notification is sent
//  to the DocStore. The scan is deferred until an EnableUpdates notification
//  is sent to DocStore.
//
//----------------------------------------------------------------------------


#include <pch.cxx>
#pragma hdrstop

#include <ciregkey.hxx>
#include <cistore.hxx>
#include <rcstxact.hxx>
#include <imprsnat.hxx>
#include <eventlog.hxx>

#include <docstore.hxx>

#include "cicat.hxx"
#include "update.hxx"
#include "notifmgr.hxx"
#include "scanmgr.hxx"
#include "scopetbl.hxx"

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::CCiScanMgr
//
//  Synopsis:   ~ctor of the scan manager for downlevel CI. It starts a
//              background thread for doing the scans.
//
//  Arguments:  [cicat] -
//
//  History:    1-19-96   srikants   Created
//              3-03-98   kitmanh    Initialized member _fIsReadOnly with
//                                   cicat.IsReadOnly
//
//  Notes:
//
//----------------------------------------------------------------------------

CCiScanMgr::CCiScanMgr( CiCat & cicat ) :
    _cicat(cicat),
    _fAbort(FALSE),
    _fSerializeChanges(FALSE),
    _state(eStart),
    #pragma warning( disable : 4355 )       // this used in base initialization
    _thrScan( ScanThread, this, TRUE ),     // create suspended
    #pragma warning( default : 4355 )
    _fBatch(FALSE),                         // disable batch processing
    _fAbortScan(FALSE),
    _fScanDisabled(FALSE),
    _fIsReadOnly(cicat.IsReadOnly()),
    _dwLastShareSynch( 0 )
{
    _evtScan.Reset();

    _thrScan.SetPriority( THREAD_PRIORITY_BELOW_NORMAL );
}


CCiScanMgr::~CCiScanMgr()
{
    InitiateShutdown();
    WaitForShutdown();

    // delete any in-progress scan info

    CLock lock( _mutex );

    while ( !_scansInProgress.IsEmpty() )
    {
        delete _scansInProgress.RemoveLast();
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::StartRecovery
//
//  Synopsis:   Sets the state to indicate that recovery must be done
//              and wakes up the scan thread.
//
//  History:    3-06-96   srikants   Created
//
//----------------------------------------------------------------------------

void CCiScanMgr::StartRecovery()
{
    CLock   lock(_mutex);
    Win4Assert( eStart == _state );
    _state = eDoRecovery;
    _evtScan.Set();
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::StartScansAndNotifies
//
//  Synopsis:   Initiates scans and notifications in the document store.
//
//  History:    12-09-96   srikants   Created
//
//----------------------------------------------------------------------------

void CCiScanMgr::StartScansAndNotifies()
{
    CLock   lock(_mutex);
    Win4Assert( eRecovered == _state );

    _state = eStartScans;
    _evtScan.Set();
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::_LokIsScanScheduled
//
//  Synopsis:   Tests if the given scope is already scheduled for a scan.
//
//  Arguments:  [xScanInfo] - Smart pointer to scaninfo
//
//  History:    2-26-96   srikants   Created
//
//----------------------------------------------------------------------------

BOOL CCiScanMgr::_LokIsScanScheduled( const XPtr<CCiScanInfo> & xScanInfo )
{
    WCHAR const * pwszNewScope = xScanInfo->GetPath();
    unsigned lenNewScope = wcslen( pwszNewScope );

    for ( CFwdScanInfoIter scanInfoIter(_scansToDo);
          !_scansToDo.AtEnd(scanInfoIter);
          _scansToDo.Advance(scanInfoIter) )
    {
        if ( xScanInfo->LokGetWorkType() == scanInfoIter->LokGetWorkType() )
        {
            WCHAR const * pwszPath = scanInfoIter->GetPath();

            CScopeMatch scope( pwszPath, wcslen(pwszPath) );
            if (scope.IsInScope( pwszNewScope, lenNewScope ))
            {
                ciDebugOut(( DEB_ITRACE,"Scan already scheduled for (%ws)\n",
                             pwszNewScope ));
                return TRUE;
            }
        }
    }

    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::ScanScope
//
//  Synopsis:   Adds the given scope to the list of scopes to scan.
//
//  Arguments:  [xScanInfo] - Will be acquired from the safe pointer if
//                            successfully taken over.
//              [fDelayed]  - Set to TRUE if the scan must not be done
//                            immediately.
//              [fRefiled]  - Set to TRUE if this is a refile or retry scan
//
//  History:    1-23-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::ScanScope(
    XPtr<CCiScanInfo> & xScanInfo,
    BOOL fDelayed,
    BOOL fRefiled )
{
    CLock lock( _mutex );

    if ( xScanInfo->GetRetries() <= CCiScanInfo::MAX_RETRIES &&
         !_LokIsScanScheduled( xScanInfo ) )
    {
        Win4Assert( !xScanInfo->LokIsInFinalState() );
        Win4Assert( xScanInfo->LokIsInScan()
                    || xScanInfo->LokIsDelScope()
                    || xScanInfo->LokIsRenameDir() );

        if ( fRefiled )
        {
            //
            // A scan that has been refiled should be done before new scans
            // to ensure that all scans are done in FIFO order.
            //
            _scansToDo.Push( xScanInfo.GetPointer() );
        }
        else
            _scansToDo.Queue( xScanInfo.GetPointer() );
        xScanInfo.Acquire();

        if ( !fDelayed )
            _evtScan.Set();
    }
    else
    {
        xScanInfo.Free();
    }
}



//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::DirAddNotification
//
//  Synopsis:   Schedules the scan of a new directory
//
//  Arguments:  [pwcsDirName] - Directory added
//
//  History:    20-Mar-96      SitaramR      Added header
//
//----------------------------------------------------------------------------

void CCiScanMgr::DirAddNotification( WCHAR const *pwcsDirName )
{
    //
    // Force a full scan of the direcotry, because the directory is new and so
    // it cannot have been scanned before
    //
    XPtr<CCiScanInfo> xScanInfo( _QueryScanInfo( pwcsDirName,
                                                 _cicat.GetPartition(),
                                                 UPD_FULL,
                                                 FALSE ) );
    xScanInfo->SetScan();
    xScanInfo->SetProcessRoot();

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

        _scansToDo.Queue( xScanInfo.GetPointer() );
        xScanInfo.Acquire();

        _evtScan.Set();        // Wake up the scan thread
    }
    //---------------------------------------------------------
}


//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::DirRenameNotification
//
//  Synopsis:   Schedules the scan of a rename directory notification
//
//  Arguments:  [pwcsDirOldName] - Previous name of irectory
//              [pwcsDirNewName] - New name of directory
//
//  History:    20-Mar-96      SitaramR      Added header
//
//----------------------------------------------------------------------------

void CCiScanMgr::DirRenameNotification( WCHAR const *pwcsDirOldName,
                                        WCHAR const *pwcsDirNewName )
{

    BOOL fRenameScheduled = FALSE;

    CLock lock( _mutex );

    for ( CFwdScanInfoIter scanInfoIter( _scansToDo );
          !_scansToDo.AtEnd( scanInfoIter );
          _scansToDo.Advance( scanInfoIter ) )
    {
        //
        // if dirA is renamed to dirB, and then dirB is renamed to dirC, then it is
        // the same as dirA being renamed to dirC.
        //
        // Note: if dirA is renamed to dirB, and then dirB is renamed to dirA, we don't
        // cancel the two renames because it may not yield the same original state. For
        // example, after the first rename if a file, say file1, below dirB is deleted, and
        // then dirB is renamed to dirA, then since the strings table is not aware of the
        // file dirB\file1, no action will be taken, ie the file won't be deleted. By
        // scheduling the two renames one after another the wid corresponding to dirB\file1
        // will be correctly deleted.
        //
        if ( scanInfoIter->LokIsRenameDir()
             && AreIdenticalPaths( scanInfoIter->GetPath(), pwcsDirOldName )
             && !AreIdenticalPaths( scanInfoIter->GetDirOldName(), pwcsDirNewName ) )  // See note above
        {
             //
             // By overwriting dirC over dirB (see example above), we have combined the two rename
             // operations into one rename operation
             //
             scanInfoIter->LokSetPath( pwcsDirNewName );
             fRenameScheduled = TRUE;

             break;
        }
    }


    if ( !fRenameScheduled )
    {
        XPtr<CCiScanInfo> xScanInfo( _QueryScanInfo( pwcsDirNewName,
                                                     _cicat.GetPartition(),
                                                     UPD_INCREM,
                                                     FALSE ) );
        xScanInfo->SetRenameDir();
        xScanInfo->SetDirOldName( pwcsDirOldName );

        ScanScope( xScanInfo, TRUE, FALSE );

        //
        // If this rename operation is interrupted in the middle (because of a
        // subsequent delete or rename) then files/wids under the old directory may
        // still be lying around in the property store. To ensure that all such
        // files/wids are removed, schedule a remove operation for the old directory
        // name.
        //

        _LokScheduleRemove( pwcsDirOldName );
    }
}



//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::_QueryScanInfo
//
//  Synopsis:   Returns a new instance of CCiScanInfo
//
//  Arguments:  [pwcsScope]    -- Scope
//              [partId]       -- Partition id
//              [updFlag]      -- Incremental or full update
//              [fDoDeletions] -- Shoud deletions be done ?
//              [fNewScope]    -- TRUE if a new scope
//
//  History:    20-Mar-96      SitaramR      Added header
//
//----------------------------------------------------------------------------

CCiScanInfo *
CCiScanMgr::_QueryScanInfo( WCHAR const * pwcsScope,
                            PARTITIONID partId,
                            ULONG updFlag,
                            BOOL fDoDeletions,
                            BOOL fNewScope )
{
    Win4Assert( 0 != pwcsScope );
    ULONG len = wcslen( pwcsScope );
    Win4Assert( pwcsScope[len-1] == L'\\' );

    XArray<WCHAR> xPath( len+1 );
    RtlCopyMemory( xPath.Get(), pwcsScope, xPath.SizeOf() );
    return new CCiScanInfo( xPath,
                            partId,
                            updFlag,
                            fDoDeletions,
                            CI_VOLID_USN_NOT_ENABLED,
                            0,
                            FALSE,
                            fNewScope );
}


//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::ScanScope
//
//  Synopsis:   Adds the given scope with given characteristics to the list
//              of paths to be scanned.
//
//  Arguments:  [pwcsScope]    - path name of scope to be added
//              [partId]       - partition ID
//              [updFlag]      -
//              [fDoDeletions] -
//              [fDelayed]     -
//              [fNewScope] - TRUE if a new scope
//
//  History:    1-19-96   srikants   Created
//
//----------------------------------------------------------------------------

void CCiScanMgr::ScanScope( WCHAR const * pwcsScope,
                            PARTITIONID partId,
                            ULONG updFlag,
                            BOOL  fDoDeletions,
                            BOOL  fDelayed,
                            BOOL fNewScope )
{
    Win4Assert( wcslen(pwcsScope) < MAX_PATH );

    XPtr<CCiScanInfo> xScanInfo( _QueryScanInfo( pwcsScope,
                                                 partId,
                                                 updFlag,
                                                 fDoDeletions,
                                                 fNewScope ) );
    xScanInfo->SetScan();
    xScanInfo->SetProcessRoot();

    ScanScope( xScanInfo, fDelayed, FALSE );
}


//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::ScheduleSerializeChanges
//
//  Synopsis:   Schedules a serialize-changes task
//
//  History:    20-Aug-97   SitaramR   Created
//
//----------------------------------------------------------------------------

void CCiScanMgr::ScheduleSerializeChanges()
{
    CLock   lock(_mutex);

    _fSerializeChanges = TRUE;
    _evtScan.Set();
}


//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::InitiateShutdown
//
//  Synopsis:   Initiates the shutdown process.
//
//  History:    2-28-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::InitiateShutdown()
{
    CLock   lock(_mutex);
    _fAbort = TRUE;
    _fAbortScan = TRUE;

    //
    // collect all the paths from the to-do stack.
    //
    while ( _scansToDo.Count() > 0 )
    {
        //
        // delete any pending scans.
        //
        delete _scansToDo.Pop();
    }

    _evtScan.Set();
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::WaitForShutdown
//
//  Synopsis:   Waits for the shutdown to complete.
//
//  History:    2-28-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::WaitForShutdown()
{
    //
    // If we never started running, then just bail out.
    //

    if ( _thrScan.IsRunning() )
    {
        ciDebugOut(( DEB_ITRACE, "Waiting for death of scan thread\n" ));
        _thrScan.WaitForDeath();
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::SetBatch
//
//  Synopsis:   Sets the flag that batch processing of scans is in progress.
//              Until the flag is turned off, the scan thread will not look
//              at the scopes for scanning.
//
//  History:    1-23-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::SetBatch()
{
    CLock   lock(_mutex);
    _fBatch = TRUE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::ClearBatch
//
//  Synopsis:   Clears the batch processing flag and wakes up the scan
//              thread. All the accumulated scopes for scanning will be
//              retrieved by the scan thread and processed.
//
//  History:    1-23-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::ClearBatch()
{
    CLock   lock(_mutex);
    _fBatch = FALSE;
    _evtScan.Set();
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::WakeUp
//
//  Synopsis:   Wakes up the scan thread.
//
//  History:    1-23-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::WakeUp()
{
    CLock   lock(_mutex);
    _evtScan.Set();
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::ScanThread
//
//  Synopsis:
//
//  Arguments:  [self] -
//
//  History:    1-19-96   srikants   Created
//              3-03-98   kitmanh    Don't _DoScans if catalog is read-only
//
//----------------------------------------------------------------------------

DWORD CCiScanMgr::ScanThread( void * self )
{
    SCODE sc = CoInitializeEx( 0, COINIT_MULTITHREADED );
    ((CCiScanMgr *) self)->_DoScans();
    CoUninitialize();

    ciDebugOut(( DEB_ITRACE, "Terminating scan thread\n" ));

    //
    // This is only necessary if thread is terminated from DLL_PROCESS_DETACH.
    //
    //TerminateThread( ((CCiScanMgr *) self)->_thrScan.GetHandle(), 0 );

    return 0;
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::SetScanSuccess, public
//
//  Synopsis:   Called on successful completion of scan.
//
//  Arguments:  [pScanInfo] -- Scope that was scanned.
//
//  History:    13-Apr-1998  KyleP  Moved to .cxx and added cicat callback.
//
//----------------------------------------------------------------------------

void CCiScanMgr::SetScanSuccess( CCiScanInfo * pScanInfo )
{
    Win4Assert( 0 != pScanInfo );

    CLock   lock(_mutex);
    if ( !_fAbort && !_fAbortScan )
    {
        pScanInfo->LokSetDone();
        _cicat.SetTreeScanComplete( pScanInfo->GetPath() );
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::_DoScans
//
//  Synopsis:
//
//  History:    1-19-96   srikants   Created
//              3-25-98   kitmanh    Just set the initialized event and return
//                                   if cat is r/o
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::_DoScans()
{
    if ( IsReadOnly() )
    {
        _cicat.SetEvtInitialized();
        _cicat.SetEvtPh2Init();
        _cicat.SynchWithRegistryScopes();
        return;
    }

    BOOL fContinue = TRUE;

    BOOL fScanned = FALSE;              // set to TRUE if a scan is performed

    while ( fContinue )
    {
        BOOL  fWait = FALSE;            // flag set to TRUE if a wait must be done

        EState workType = eStart;

        BOOL  fShortWait = FALSE;

        BOOL fSerializeChanges = FALSE; // No serialize-changes tasks yet

        NTSTATUS status = STATUS_SUCCESS;

        //
        // Don't do any work until the system has booted
        //

        while ( ( GetTickCount() < _cicat.GetRegParams()->GetStartupDelay() ) &&
                ( eStart != _state ) &&
                ( eDoRecovery != _state ) )
        {
            Sleep( 200 );
            if ( _fAbort )
                break;
        }

        TRY
        {
            XPtr<CCiScanInfo>   xScanInfo;

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

                if ( _fAbort )
                    break;

                if ( !_fBatch && _LokIsOkToScan() )
                {
                    //
                    // refile any incomplete paths that could not be
                    // refiled due to low resources.
                    //
                    if ( _scansInProgress.Count() > 0 )
                    {
                        _LokEmptyInProgressScans();
                    }

                    //
                    // collect all the paths from the to-do stack.
                    //
                    while ( _scansToDo.Count() > 0 )
                    {
                        // should first save in a safe pointer because the
                        // push can fail.
                        xScanInfo.Set( _scansToDo.Pop() );
                        if ( !_fScanDisabled && !xScanInfo->LokIsInFinalState() )
                        {
                            _scansInProgress.Queue( xScanInfo.GetPointer() );
                            xScanInfo.Acquire();
                        }
                        else
                        {
                            // this scope is deleted
                            xScanInfo.Free();
                        }
                    }

                    if ( 0 == _scansInProgress.Count() )
                        _evtScan.Reset();

                    if ( 0 == _scansInProgress.Count() && _fSerializeChanges )
                    {
                        //
                        // Make local copy of fSerializeChanges for use outside lock. Also
                        // reset _fSerializeChanges since a flush task will be scheduled below.
                        //
                        fSerializeChanges = TRUE;
                        _fSerializeChanges = FALSE;
                    }

                }
                else if ( _LokIsDoRecovery() )
                {
                    _evtScan.Reset();
                    workType = eDoRecovery;
                }
                else if ( _LokIsStartScans() )
                {
                    _evtScan.Reset();
                    workType = eStartScans;
                }
            }
            // =========================================

            //
            // Update fixups.  We have to do this at regular
            // intervals because there is no notification API for
            // share changes.  Check no more often than every 15
            // minutes; this drags in 13 DLLs.
            //

            DWORD cmsDifference = GetTickCount() - _dwLastShareSynch;

            if ( cmsDifference > ( _cicat.GetRegParams()->MaxAutoAliasRefresh() * 1000 * 60 ) )
            {
                //
                // Don't do this in resource-bound situations
                //

                CI_STATE State;
                State.cbStruct = sizeof( State );

                SCODE sc = _cicat.CiState( State );

                if ( SUCCEEDED( sc ) &&
                     ( 0 == ( State.eState & ( CI_STATE_HIGH_IO |
                                               CI_STATE_LOW_MEMORY |
                                               CI_STATE_USER_ACTIVE ) ) ) )
                {
                    _cicat.SynchShares();

                    //
                    // it is OK to modify this outside the class lock because
                    // only one thread performs scans for a catalog at any moment
                    // and there is one CCiScanMgr object per catalog.
                    //
                    _dwLastShareSynch = GetTickCount();
                }
            }

            if ( eDoRecovery == workType )
            {
                //
                // Do the long running initialization.
                //

                Win4Assert( !IsReadOnly() );


                // Note: recovery is now synchronous, but this must be
                // done asynchronously, since the callback to the docstore
                // must be done by a worker thread.
                //_cicat.DoRecovery();

                //
                // Set the state of the scan manager as recovered.
                //
                // ======================================
                {
                    CLock   lock(_mutex);
                    _state = eRecovered;
                }
                // ======================================

                ciDebugOut(( DEB_WARN, "Setting CiCat recovery done...\n" ));

                _cicat.SetRecoveryCompleted();
            }
            else if ( eStartScans == workType )
            {
                _cicat.StartScansAndNotifies();

                CLock   lock(_mutex);
                _state = eNormal;
            }
            else if ( _scansInProgress.Count() > 0 )
            {
                fScanned = TRUE;
                _Scan();
                Win4Assert( 0 == _scansInProgress.Count() || _fAbort );
            }
            else if ( fSerializeChanges )
            {
                _cicat.SerializeChangesInfo();
                fWait = TRUE;
            }
            else
            {
                fWait = TRUE;
            }

            //
            // Do scans complete processing if appropriate.
            //
            if ( fWait &&
                 eNormal == _state &&
                 !_fScanDisabled &&
                 !fSerializeChanges )
            {
                _cicat.ProcessScansComplete( fScanned, fShortWait );
            }
        }
        CATCH (CException, e)
        {
            status = e.GetErrorCode();

            ciDebugOut(( DEB_ERROR,
                "CCiScanMgr::_DoScans. Caught exception 0x%X\n",
                status ));

            if ( CiCat::IsDiskLowError( status )         ||
                 STATUS_INSUFFICIENT_RESOURCES == status ||
                 STATUS_NO_MEMORY == status )
            {
                // delay the execution of the thread until resources are
                // available.
                fWait = TRUE;
            }
            else
            {
                _cicat.HandleError( status );
                fContinue = FALSE;
            }

            // We did not successfully complete recovery, but we need to signal that
            // phase 2 init is complete (albeit unsuccessfully)
            // fix for bug 151799
            if (_cicat.IsCorrupt() && eDoRecovery == workType)
                _cicat.SignalPhase2Completion();
        }
        END_CATCH

        if ( fWait )
        {
            fScanned = FALSE;

            //
            // If we are waiting during long initialization, then have a
            // shorter wait time to see if the error condition has cleared
            // up.
            //
 
            DWORD dwWaitTime = ( (eStart != workType) || fShortWait) ?
                               PREINIT_WAIT : AUTOSCAN_WAIT;
 
            dwWaitTime = min( dwWaitTime,
                              _cicat.GetRegParams()->GetForcedNetPathScanInterval() * 60 * 1000 );
 
            dwWaitTime = min( dwWaitTime,
                              _cicat.GetRegParams()->MaxAutoAliasRefresh() * 1000 * 60  );
 
            _evtScan.Wait( dwWaitTime );
        }
    }
} //_DoScans

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::_LokEmptyInProgressScans
//
//  Synopsis:   Removes all the scans from the "in-progress stack" and either
//              deletes them or re-schedules them. If the scan is in its
//              "terminal state", the scan is deleted. If there is a retry
//              it will be re-scheduled.
//
//  History:    1-25-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::_LokEmptyInProgressScans()
{
    //
    // refile any scopes that still need to be worked on.
    //
    while ( _scansInProgress.Count() > 0 )
    {
        if ( _fAbort )
            break;

        XPtr<CCiScanInfo>   xScanInfo( _scansInProgress.RemoveLast() );

        if ( !_fScanDisabled && !xScanInfo->LokIsInFinalState() )
        {

#if CIDBG==1
            if ( xScanInfo->LokIsDelScope() )
            {
                ciDebugOut(( DEB_ITRACE, "Requeing scope (%ws) for removal\n",
                             xScanInfo->GetPath() ));
            }
            else if ( xScanInfo->LokIsRenameDir() )
            {
                ciDebugOut(( DEB_ITRACE, "Requeing scope (%ws) for rename\n",
                             xScanInfo->GetPath() ));
            }
            else
            {
                ciDebugOut(( DEB_ITRACE, "Requeuing scope (%ws) for scan\n",
                         xScanInfo->GetPath() ));
            }
#endif  // CIDBG==1

            ScanScope( xScanInfo,
                       xScanInfo->LokIsRetry(),  // delay for retry
                       TRUE );                   // It's a refiled scan
        }
        else
        {
            xScanInfo.Free();
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::_Scan
//
//  Synopsis:
//
//  Returns:
//
//  Modifies:
//
//  History:    1-25-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::_Scan()
{

    // does not THROW
    _cicat.DoUpdate( _scansInProgress, *this, _fAbortScan );

    // =============================================================
    {
        CLock   lock(_mutex);
        _LokEmptyInProgressScans();
        _fAbortScan = FALSE;
    }
    // =============================================================
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::_LokScheduleRemove
//
//  Synopsis:   Schedules a path for removal.
//
//  Arguments:  [pwscScope] - Scope to be removed from CiCat.
//
//  History:    1-26-96   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CCiScanMgr::_LokScheduleRemove( WCHAR const * pwcsScope )
{
    CCiScanInfo * pScanInfo = _QueryScanInfo( pwcsScope,
                                             _cicat.GetPartition(),
                                             UPD_INCREM, TRUE );
    XPtr<CCiScanInfo>   xScanInfo( pScanInfo );
    pScanInfo->LokSetDelScope();
    ScanScope( xScanInfo, FALSE, FALSE );
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::RemoveScope
//
//  Synopsis:   Removes the scope from any active or in-progress scans and
//              marks it for "deletions". If there is no active or in-progress
//              scan for the scope, a new "deletion scan" will be scheduled.
//
//  Arguments:  [pwcsScope] - The scope to be removed.
//
//  History:    1-25-96   srikants   Created
//
//  Notes:      This method must not only remove the scope from the scheduled
//              scans but also from any currently in-progress scans. It is
//              possible that the scan thread is currently working on the
//              path to be removed. In that case, we set the state of the
//              scope to indicate that it must be aborted.
//
//----------------------------------------------------------------------------

void CCiScanMgr::RemoveScope( WCHAR const * pwcsScope )
{
    //
    // if the given scope is in the list of paths being currently
    // scanned, we must mark it deleted.
    //
    BOOL fRemoved = FALSE;

    // ===========================================================
    {
        CLock   lock(_mutex);
        if ( _fAbort )
            return;

        for ( CFwdScanInfoIter scanInfoIter1( _scansToDo );
              !_scansToDo.AtEnd( scanInfoIter1 );
              _scansToDo.Advance( scanInfoIter1 ) )
        {
            WCHAR const * pwcsPath = scanInfoIter1->GetPath();

            if ( AreIdenticalPaths( pwcsScope, pwcsPath ) )
            {
                fRemoved = TRUE;
                if ( !scanInfoIter1->LokIsDelScope() )
                    scanInfoIter1->LokSetDelScope();
            }
        }

        //
        // Next see in the list of paths being currently scanned.
        //
        for ( CFwdScanInfoIter scanInfoIter2( _scansInProgress );
              !_scansInProgress.AtEnd( scanInfoIter2 );
              _scansInProgress.Advance( scanInfoIter2 ) )
        {
            WCHAR const * pwcsPath = scanInfoIter2->GetPath();

            if ( AreIdenticalPaths( pwcsScope, pwcsPath ) )
            {
                fRemoved = TRUE;

                if ( !scanInfoIter2->LokIsDelScope() )
                {
                    _fAbortScan = TRUE;
                    scanInfoIter2->LokSetDelScope();
                }
            }
        }

        if ( !fRemoved )
        {
            _LokScheduleRemove( pwcsScope );
            fRemoved = TRUE;
        }

        _evtScan.Set();     // wake up the scan thread.

        Win4Assert( fRemoved );
    }
    // ===========================================================
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::DisableScan
//
//  Synopsis:   Disables further scans and aborts any in progress.
//
//  History:    4-16-96   srikants   Created
//
//----------------------------------------------------------------------------

void CCiScanMgr::DisableScan()
{
    CLock   lock(_mutex);

    _fAbortScan   = TRUE;
    _fScanDisabled = TRUE;

    _evtScan.Set();
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::EnableScan
//
//  Synopsis:   Re-enables scanning if scanning is currently disabled.
//
//  History:    4-16-96   srikants   Created
//
//----------------------------------------------------------------------------

void CCiScanMgr::EnableScan()
{
    CLock   lock(_mutex);
    if ( _fScanDisabled )
    {
        _fScanDisabled = FALSE;
        _evtScan.Set();
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CCiScanMgr::AnyInitialScans
//
//  Synopsis:   Checks if any scans are the result of a new scope
//
//  Returns:    TRUE if any scans are for new scopes
//
//  History:    3-Aug-98   dlee   Created
//
//----------------------------------------------------------------------------

BOOL CCiScanMgr::AnyInitialScans()
{
    for ( CFwdScanInfoIter iter1( _scansToDo );
          !_scansToDo.AtEnd( iter1 );
          _scansToDo.Advance( iter1 ) )
    {
        if ( iter1->IsNewScope() )
            return TRUE;
    }

    for ( CFwdScanInfoIter iter2( _scansInProgress );
          !_scansInProgress.AtEnd( iter2 );
          _scansInProgress.Advance( iter2 ) )
    {
        if ( iter2->IsNewScope() )
            return TRUE;
    }

    return FALSE;
} //AnyInitialScans