/*++

   Copyright    (c)    1999    Microsoft Corporation

   Module Name :
     usercache.cxx

   Abstract:
     Implements the common code shared by all the caches
     (file, meta, uri, token, ul-cached-response, etc)
 
   Author:
     Bilal Alam (balam)             11-Nov-2000

   Environment:
     Win32 - User Mode

   Project:
     ULW3.DLL
--*/

#include "precomp.hxx"
#include "usercache.hxx"
#include "cachehint.hxx"

//static 
void
CACHE_ENTRY_HASH::AddRefRecord(
    CACHE_ENTRY *           pCacheEntry,
    int                     nIncr 
)
/*++

Routine Description:

    Dereference and possibly delete the cache entry.  Note the destructor
    for the cache entry is private.  Only this code should ever delete
    the cache entry

Arguments:

    None

Return Value:

    None

--*/
{
    DBG_ASSERT( pCacheEntry->CheckSignature() );
        
    if ( nIncr == +1 )
    {
        pCacheEntry->ReferenceCacheEntry();
    }
    else if ( nIncr == -1 )
    {
        pCacheEntry->SetFlushed();
        pCacheEntry->QueryCache()->IncFlushes();
        pCacheEntry->QueryCache()->DecEntriesCached();
        pCacheEntry->DereferenceCacheEntry();        
    }
    else 
    {
        DBG_ASSERT( FALSE );
    }
}

//
// CACHE_ENTRY definitions
//

CACHE_ENTRY::CACHE_ENTRY(
    OBJECT_CACHE *      pObjectCache,
    DWORD               cTTLOverride
)
{
    _cRefs = 1;
    _fFlushed = FALSE;
    _fCached = FALSE;
    if ( cTTLOverride != 0 )
    {
        _cConfiguredTTL = cTTLOverride;
    }
    else
    {
        _cConfiguredTTL = pObjectCache->QueryConfiguredTTL();
    }
    _cTTL = _cConfiguredTTL;
    _pObjectCache = pObjectCache;
    _pDirmonInvalidator = NULL;
    
    _dwSignature = CACHE_ENTRY_SIGNATURE;
}

CACHE_ENTRY::~CACHE_ENTRY(
    VOID
)
{
    DBG_ASSERT( _cRefs == 0 );

    if ( _pDirmonInvalidator != NULL )
    {
        _pDirmonInvalidator->Release();
        _pDirmonInvalidator = NULL;
    }
    
    if ( _fFlushed )
    {
        DBG_ASSERT( QueryCache() != NULL );

        QueryCache()->DecActiveFlushedEntries();
    }
    
    _dwSignature = CACHE_ENTRY_SIGNATURE_FREE;
}

VOID
CACHE_ENTRY::ReferenceCacheEntry(
    VOID
)
/*++

Routine Description:

    Reference the given entry (duh)

Arguments:

    None

Return Value:

    None

--*/
{
    LONG                cRefs;
    
    cRefs = InterlockedIncrement( &_cRefs );
    
    DBG_ASSERT( QueryCache() != NULL );
    QueryCache()->DoReferenceTrace( this, cRefs );
}

VOID
CACHE_ENTRY::DereferenceCacheEntry(
    VOID
)
/*++

Routine Description:

    Dereference and possibly delete the cache entry.  Note the destructor
    for the cache entry is private.  Only this code should ever delete
    the cache entry

Arguments:

    None

Return Value:

    None

--*/
{
    LONG                cRefs;
    OBJECT_CACHE *      pObjectCache = QueryCache();
    
    DBG_ASSERT( pObjectCache != NULL );
    
    cRefs = InterlockedDecrement( &_cRefs );

    pObjectCache->DoReferenceTrace( this, cRefs );
    
    if ( cRefs == 0 )
    {
        delete this;
    }
}

HRESULT
CACHE_ENTRY::AddDirmonInvalidator(
    DIRMON_CONFIG *     pDirmonConfig
)
/*++

Routine Description:

    Setup dirmon invalidation for this cache entry        

Arguments:

    pDirmonConfig - path/token for use in monitoring directory

Return Value:

    HRESULT 

--*/
{
    CDirMonitorEntry *          pDME = NULL;
    HRESULT                     hr;
    
    hr = QueryCache()->AddDirmonInvalidator( pDirmonConfig, &pDME );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    DBG_ASSERT( pDME != NULL );

    //
    // Cleanup any old dir monitor entry
    //
   
    if ( _pDirmonInvalidator != NULL )
    {
        _pDirmonInvalidator->Release();
    }
    
    _pDirmonInvalidator = pDME;
    
    return NO_ERROR;
}

BOOL
CACHE_ENTRY::Checkout(
    CACHE_USER *
)
/*++

Routine Description:

    Checkout a cache entry
        
Arguments:

    None

Return Value:

    TRUE if the checkout was successful, else FALSE 

--*/
{
    ReferenceCacheEntry();
    
    if ( QueryIsFlushed() )
    {
        DereferenceCacheEntry();
        QueryCache()->IncMisses(); 
        return FALSE;
    }
    else
    {
        InterlockedExchange( &_cTTL, _cConfiguredTTL );
        QueryCache()->IncHits();
        return TRUE;
    }
}

BOOL
CACHE_ENTRY::QueryIsOkToFlushTTL(
    VOID
)
/*++

Routine Description:
        
    Called when the cache scavenger is invoked.  This routine returns whether
    it is OK to flush this entry due to TTL

Arguments:

    None

Return Value:

    TRUE if it is OK to flush by TTL, else FALSE

--*/
{
    //
    // Only do the TTL thing if the hash table holds the only reference to
    // the cache entry.  We can be loose with this check as this is just an
    // optimization to prevent overzealous flushing
    //
    
    if ( _cRefs > 1 )
    {
        return FALSE;
    }
    
    if ( InterlockedDecrement( &_cTTL ) == 0 ) 
    {
        //
        // TTL has expired.  However, we let the cache entry override this
        // expiry it wants to.  Check that now
        //
            
        //
        // Entry be gone!
        //
         
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

BOOL
CACHE_ENTRY::QueryIsOkToFlushMetadata(
    WCHAR *                     pszMetaPath,
    DWORD                       cchMetaPath
)
/*++

Routine Description:

    Called whem metadata has changed.  This routine returns whether the
    current cache entry should be flushed        

Arguments:

    pszMetaPath - Metabase path which changed
    cchMetaPath - Size of metabase path

Return Value:

    TRUE if we should flush

--*/
{
    BOOL                fRet;

    DBG_ASSERT( pszMetaPath != NULL );
    DBG_ASSERT( cchMetaPath != 0 );
    
    if ( pszMetaPath[ cchMetaPath - 1 ] == L'/' )
    {
        cchMetaPath--;
    }
    
    DBG_ASSERT( QueryCache()->QuerySupportsMetadataFlush() );

    if ( QueryMetadataPath() == NULL )
    {
        fRet = TRUE;
    }
    else if ( QueryMetadataPath()->QueryCCH() < cchMetaPath )
    {
        fRet = FALSE;
    }
    else if ( _wcsnicmp( QueryMetadataPath()->QueryStr(),
              pszMetaPath,
              cchMetaPath ) == 0 )
    {
        fRet = TRUE;
    }
    else
    {
        fRet = FALSE;
    }
    
    return fRet;
}

//
// OBJECT_CACHE definitions
//

OBJECT_CACHE::OBJECT_CACHE(
    VOID
)
/*++

Routine Description:

    Create an object cache.  Obviously all the app-specific goo will be 
    initialized in the derived class

Arguments:

    None

Return Value:

    None

--*/
{
    _hTimer = NULL;
    _pHintManager = NULL;
    _cmsecScavengeTime = 0;
    _cmsecTTL = 0;
    _dwSupportedInvalidation = 0;

    _cCacheHits = 0;
    _cCacheMisses = 0;
    _cCacheFlushes = 0;
    _cActiveFlushedEntries = 0;
    _cFlushCalls = 0;
    _cEntriesCached = 0;
    _cTotalEntriesCached = 0;

    _cPerfCacheHits = 0;
    _cPerfCacheMisses = 0;
    _cPerfCacheFlushes = 0;
    _cPerfFlushCalls = 0;
    _cPerfTotalEntriesCached = 0;
    
#if DBG
    _pTraceLog = CreateRefTraceLog( 2000, 0 );
#else
    _pTraceLog = NULL;
#endif
    
    InitializeListHead( &_listEntry );

    _dwSignature = OBJECT_CACHE_SIGNATURE;
}

VOID
OBJECT_CACHE::UnregisterScavenger(
    VOID
)
{
    if ( _hTimer != NULL )
    {
        DeleteTimerQueueTimer( NULL,
                               _hTimer,
                               INVALID_HANDLE_VALUE );
        
        _hTimer = NULL;
    }
}
    
OBJECT_CACHE::~OBJECT_CACHE(
    VOID
)
{
    _dwSignature = OBJECT_CACHE_SIGNATURE_FREE;

    UnregisterScavenger();
   
    if ( _pHintManager != NULL )
    {
        delete _pHintManager;
        _pHintManager = NULL;
    }
    
    if ( _pTraceLog != NULL )
    {
        DestroyRefTraceLog( _pTraceLog );
        _pTraceLog = NULL;
    }
}

VOID
OBJECT_CACHE::CleanupCacheEntryListItems(
    LIST_ENTRY        * pListHead
    )
{
    CACHE_ENTRY * pCacheEntry;

    //
    // Free cache entry list items
    //

    while ( !IsListEmpty( pListHead ) ) 
    {
        pCacheEntry = CONTAINING_RECORD( pListHead->Flink,
                                         CACHE_ENTRY,
                                         _listEntry );

        RemoveEntryList( &pCacheEntry->_listEntry );
        pCacheEntry->_listEntry.Flink = NULL;

        pCacheEntry->DereferenceCacheEntry();
    }
}

//static
VOID
WINAPI
OBJECT_CACHE::ScavengerCallback(
    PVOID                   pParam,
    BOOLEAN             
)
{
    OBJECT_CACHE *          pObjectCache;
    LIST_ENTRY              listHead;
    
    pObjectCache = (OBJECT_CACHE*) pParam;
    DBG_ASSERT( pObjectCache != NULL );
    DBG_ASSERT( pObjectCache->CheckSignature() );
    
    InitializeListHead( &listHead);

    pObjectCache->FlushByTTL( &listHead );
    
    //
    // Remove all cache entries in the cache entry list
    //
    pObjectCache->CleanupCacheEntryListItems( &listHead ); 
}

//static
LK_PREDICATE
OBJECT_CACHE::CacheFlushByRegChange(
    CACHE_ENTRY *           pCacheEntry,
    VOID *                  pvState
)
/*++

Routine Description:

    Determine whether given entry should be deleted due to TTL change.

Arguments:

    pCacheEntry - Cache entry to check
    pvState - Pointer to cache

Return Value:

    LKP_PERFORM - do the delete,
    LKP_NO_ACTION - do nothing

--*/
{
    LIST_ENTRY *  pListHead = static_cast<LIST_ENTRY *>( pvState );
    
    DBG_ASSERT( pCacheEntry != NULL );
    DBG_ASSERT( pCacheEntry->CheckSignature() );
    
    pCacheEntry->ReferenceCacheEntry();
    
    InsertHeadList( 
          pListHead, 
          &pCacheEntry->_listEntry );

    return LKP_PERFORM;
}

//static
LK_PREDICATE
OBJECT_CACHE::CacheFlushByTTL(
    CACHE_ENTRY *           pCacheEntry,
    VOID *                  pvState
)
/*++

Routine Description:

    Determine whether given entry should be deleted due to TTL

Arguments:

    pCacheEntry - Cache entry to check
    pvState - Pointer to cache

Return Value:

    LKP_PERFORM - do the delete,
    LKP_NO_ACTION - do nothing

--*/
{
    LIST_ENTRY *  pListHead = static_cast<LIST_ENTRY *>( pvState );
    
    DBG_ASSERT( pCacheEntry != NULL );
    DBG_ASSERT( pCacheEntry->CheckSignature() );
    
    if ( pCacheEntry->QueryIsOkToFlushTTL() )
    {   
        pCacheEntry->ReferenceCacheEntry();
        
        InsertHeadList( 
              pListHead, 
              &pCacheEntry->_listEntry );

        return LKP_PERFORM;
    }
    else
    {
        return LKP_NO_ACTION;
    }
}

//static
LK_PREDICATE
OBJECT_CACHE::CacheFlushByDirmon(
    CACHE_ENTRY *           pCacheEntry,
    VOID *                  pvState
)
/*++

Routine Description:

    Determine whether given entry should be deleted due to dir mon

Arguments:

    pCacheEntry - Cache entry to check
    pvState - STRU of path which changed

Return Value:

    LKP_PERFORM - do the delete,
    LKP_NO_ACTION - do nothing

--*/
{
    STRU *          pstrPath = (STRU*) pvState;
    OBJECT_CACHE *  pCache;
    
    DBG_ASSERT( pCacheEntry != NULL );
    DBG_ASSERT( pCacheEntry->CheckSignature() );
    
    pCache = pCacheEntry->QueryCache();
    DBG_ASSERT( pCache->CheckSignature() );
    
    if ( pCacheEntry->QueryIsOkToFlushDirmon( pstrPath->QueryStr(),
                                              pstrPath->QueryCCH() ) )
    {
        return LKP_PERFORM;
    }
    else
    {
        return LKP_NO_ACTION;
    }
}

//static
LK_PREDICATE
OBJECT_CACHE::CacheFlushByMetadata(
    CACHE_ENTRY *           pCacheEntry,
    VOID *                  pvState
)
/*++

Routine Description:

    Determine whether given entry should be deleted due to metadata change

Arguments:

    pCacheEntry - Cache entry to check
    pvState - STRU with metapath which changed

Return Value:

    LKP_PERFORM - do the delete,
    LKP_NO_ACTION - do nothing

--*/
{
    STRU *          pstrPath = (STRU*) pvState;
    OBJECT_CACHE *  pCache;
    
    DBG_ASSERT( pCacheEntry != NULL );
    DBG_ASSERT( pCacheEntry->CheckSignature() );
    
    pCache = pCacheEntry->QueryCache();
    DBG_ASSERT( pCache->CheckSignature() );
    
    if ( pCacheEntry->QueryIsOkToFlushMetadata( pstrPath->QueryStr(),
                                                pstrPath->QueryCCH() ) )
    {
        return LKP_PERFORM;
    }
    else
    {
        return LKP_NO_ACTION;
    }
}

VOID
OBJECT_CACHE::FlushByRegChange(
    LIST_ENTRY        * pListHead
)
/*++

Routine Description:

    Flush any inactive cache entries who have outlived their TTL

Arguments:

    pListHead - Head of the cache entry list to be deleted

Return Value:

    None

--*/
{
    IncFlushCalls();
    
    //
    // Iterate the hash table, deleting expired itmes
    //
    
    _hashTable.DeleteIf( CacheFlushByRegChange, pListHead );
}

VOID
OBJECT_CACHE::FlushByTTL(
    LIST_ENTRY        * pListHead
)
/*++

Routine Description:

    Flush any inactive cache entries who have outlived their TTL

Arguments:

    pListHead - Head of the cache entry list to be deleted

Return Value:

    None

--*/
{
    IncFlushCalls();
    
    //
    // Iterate the hash table, deleting expired itmes
    //
    
    _hashTable.DeleteIf( CacheFlushByTTL, pListHead );
}

VOID
OBJECT_CACHE::DoDirmonInvalidationFlush(
    WCHAR *             pszPath
)
/*++

Routine Description:

    Flush all appropriate entries due to dirmon change notification

Arguments:

    pszPath - Path that changed

Return Value:

    None

--*/
{
    STACK_STRU(             strPath, 256 );
    
    IncFlushCalls();
    
    if ( SUCCEEDED( strPath.Copy( pszPath ) ) )
    {
        _hashTable.DeleteIf( CacheFlushByDirmon, (VOID*) &strPath );
    }
}

VOID
OBJECT_CACHE::DoMetadataInvalidationFlush(
    WCHAR *             pszMetaPath
)
/*++

Routine Description:

    Flush all appropriate entries due to metadata change notification

Arguments:

    pszMetaPath - Metabase path which changed

Return Value:

    None

--*/
{
    STACK_STRU(             strPath, 256 );
    
    IncFlushCalls();
    
    if ( SUCCEEDED( strPath.Copy( pszMetaPath ) ) )
    {
        _hashTable.DeleteIf( CacheFlushByMetadata, (VOID*) &strPath );
    }
}

HRESULT
OBJECT_CACHE::SetCacheConfiguration(
    DWORD                   cmsecScavengeTime,
    DWORD                   cmsecTTL,
    DWORD                   dwSupportedInvalidation,
    CACHE_HINT_CONFIG *     pCacheHintConfig
)
/*++

Routine Description:

    Do the general cache initialization here

Arguments:

    cmsecScavengeTime - How often should a scavenger be run for this cache
                        (should be no larger than the TTL expected for 
                        entries in this cache)
    cmsecTTL - TTL for entries in this cache
    dwSupportedInvalidation - How can the cache be invalidated?
    pCacheHintConfig - Cache hint configuration (NULL for no cache hints)
    
Return Value:
    
    HRESULT

--*/
{
    BOOL                    fRet;
    HRESULT                 hr;

    DBG_ASSERT( cmsecTTL >= cmsecScavengeTime );
    
    //
    // Create a timer which fires every cmsecScavengeTime
    // 
   
    _cmsecScavengeTime = cmsecScavengeTime;
    
    fRet = CreateTimerQueueTimer( &_hTimer,
                                  NULL,
                                  OBJECT_CACHE::ScavengerCallback,
                                  this,
                                  _cmsecScavengeTime,
                                  _cmsecScavengeTime,
                                  WT_EXECUTELONGFUNCTION );
    if ( !fRet )
    {
        return HRESULT_FROM_WIN32( GetLastError() );
    }
    
    _cmsecTTL = cmsecTTL;
    
    _dwSupportedInvalidation = dwSupportedInvalidation;

    //
    // Should we setup a cache hint table
    //
    
    if ( pCacheHintConfig != NULL )
    {
        _pHintManager = new CACHE_HINT_MANAGER;
        if ( _pHintManager == NULL )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
        
            DeleteTimerQueueTimer( NULL,
                                   _hTimer,
                                   INVALID_HANDLE_VALUE );
            _hTimer = NULL;
            
            return hr;
        }

        hr = _pHintManager->Initialize( pCacheHintConfig );
        if ( FAILED( hr ) )
        {   
             delete _pHintManager;
             _pHintManager = NULL;

            DeleteTimerQueueTimer( NULL,
                                   _hTimer,
                                   INVALID_HANDLE_VALUE );
            _hTimer = NULL;
        
            return hr;
        }
    }    

    return NO_ERROR;
}

HRESULT
OBJECT_CACHE::FindCacheEntry(
    CACHE_KEY *                 pCacheKey,
    CACHE_ENTRY **              ppCacheEntry,
    BOOL *                      pfShouldCache
)
/*++

Routine Description:

    Lookup key in cache

Arguments:

    pCacheKey - Cache key to lookup
    ppCacheEntry - Points to cache entry on success
    pfShouldCache - Provides a hint if possible on whether we should cache
                    (can be NULL indicating no hint is needed)

Return Value:

    HRESULT

--*/
{
    LK_RETCODE                  lkrc;
    HRESULT                     hr;
    CACHE_ENTRY *               pCacheEntry = NULL;
    
    if ( ppCacheEntry == NULL ||
         pCacheKey == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    *ppCacheEntry = NULL;

    //
    // First do a lookup
    //    
    
    lkrc = _hashTable.FindKey( pCacheKey, &pCacheEntry );
    if ( lkrc == LK_SUCCESS )
    {
        //
        // If this entry has been flushed, then it really isn't a hit
        //
        
        if ( pCacheEntry->QueryIsFlushed() )
        {
            pCacheEntry->DereferenceCacheEntry();
        }
        else
        {
            IncHits();
            
            DBG_ASSERT( pCacheEntry != NULL );
        
            *ppCacheEntry = pCacheEntry;
            
            return NO_ERROR;
        }
    }
   
    IncMisses(); 
    
    //
    // Is a hint requested?
    //
    
    if ( pfShouldCache != NULL )
    {
        *pfShouldCache = TRUE;
        
        if ( _pHintManager != NULL )
        {
            hr = _pHintManager->ShouldCacheEntry( pCacheKey,
                                                  pfShouldCache );
            
            //
            // Ignore error
            //
        }        
    }

    return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
}

HRESULT
OBJECT_CACHE::FlushCacheEntry(
    CACHE_KEY *             pCacheKey
)
/*++

Routine Description:

    Flush given cache key 

Arguments:

    pCacheKey - Key to flush

Return Value:

    HRESULT

--*/
{
    LK_RETCODE                  lkrc;
    CACHE_ENTRY *               pCacheEntry;
        
    if ( pCacheKey == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    //
    // First do a lookup
    //    
    
    lkrc = _hashTable.FindKey( pCacheKey, &pCacheEntry );
    if ( lkrc == LK_SUCCESS )
    {
        DBG_ASSERT( pCacheEntry != NULL );
        
        if ( !pCacheEntry->QueryIsFlushed() )
        {
            _hashTable.DeleteRecord( pCacheEntry );
        }
        
        pCacheEntry->DereferenceCacheEntry();
    }
    
    return NO_ERROR;
}

HRESULT
OBJECT_CACHE::AddCacheEntry(
    CACHE_ENTRY *              pCacheEntry
)
/*++

Routine Description:

    Lookup key in cache

Arguments:

    pCacheEntry - Points to cache entry on success

Return Value:

    HRESULT

--*/
{
    LK_RETCODE          lkrc;
    
    if ( pCacheEntry == NULL )
    {
        DBG_ASSERT( FALSE );
        return HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER );
    }
    
    DBG_ASSERT( pCacheEntry->QueryCached() == FALSE );
    
    //
    // Now try to insert into hash table
    //

    pCacheEntry->SetCached( TRUE );

    //
    // Replace a stale entry, if present
    //
    lkrc = _hashTable.InsertRecord( pCacheEntry, TRUE );
    if ( lkrc == LK_SUCCESS )
    {
        IncEntriesCached();
    }
    else
    {
        pCacheEntry->SetCached( FALSE );
    }

    return NO_ERROR;
}

HRESULT
OBJECT_CACHE::AddDirmonInvalidator(
    DIRMON_CONFIG *         pDirmonConfig,
    CDirMonitorEntry **     ppDME
)
/*++

Routine Description:

    Add dirmon invalidator for this cache

Arguments:

    pDirmonConfig - Configuration of dir monitor
    ppDME - filled with dir monitor entry to be attached to cache entry

Return Value:

    HRESULT

--*/
{
    HRESULT                     hr = NO_ERROR;
    
    if ( !QuerySupportsDirmonSpecific() &&
         !QuerySupportsDirmonFlush() )
    {
        return HRESULT_FROM_WIN32( ERROR_NOT_SUPPORTED );
    }
    
    //
    // Start monitoring
    //
    
    DBG_ASSERT( g_pCacheManager != NULL );
    
    hr = g_pCacheManager->MonitorDirectory( pDirmonConfig,
                                            ppDME );
    if ( FAILED( hr ) )
    {
        return hr;
    }
    
    DBG_ASSERT( *ppDME != NULL );
    
    return NO_ERROR;
}