// Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// f32\sfat\sl_dir_cache.cpp
//
//

//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!
//!! WARNING!! DO NOT edit this file !! '\sfat' component is obsolete and is not being used. '\sfat32'replaces it
//!!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


#include "sl_std.h"
#include "sl_dir_cache.h"

//======================================================================
TDynamicDirCachePage::~TDynamicDirCachePage()
    {
    }

/**
The static cache page creation function.
Cache page objects are not supposed to be created on the stack, so this factory function is required.
*/
TDynamicDirCachePage* TDynamicDirCachePage::NewL(CDynamicDirCache* aOwnerCache, TInt64 aStartMedPos, TUint8* aStartRamAddr)
    {
    return new(ELeave) TDynamicDirCachePage(aOwnerCache, aStartMedPos, aStartRamAddr);
    }

/**
Cache page constructor.
@param  aOwnerCache pointer of the cache that owns this page
@param  aStartMedPos    the start address on the media that this page caches
@param  aStartRamAddr   the start address in the ram that this page content lives
*/
TDynamicDirCachePage::TDynamicDirCachePage(CDynamicDirCache* aOwnerCache, TInt64 aStartMedPos, TUint8* aStartRamAddr)
:iStartMedPos(aStartMedPos),
iStartRamAddr(aStartRamAddr),
iOwnerCache(aOwnerCache),
iValid(EFalse),
iLocked(EFalse)
    {
//  __PRINT3(_L("TDynamicDirCachePage::TDynamicDirCachePage(aStartMedPos=%lx, aStartRamAddr=0x%X, aPageSize=%u)"), aStartMedPos, aStartRamAddr, PageSizeInBytes());
    iType = EUnknown;
    }

/////////////////////////////// class CDynamicDirCache::TLookupEntry ///////////////////////////
/**
Required by RHashSet<TLookupEntry> to identify individual hash set entries.
@see    RHashSet
*/
TBool IdentityFunction(const TLookupEntry& aEntry1, const TLookupEntry& aEntry2)
    {
    // only check starting med pos for hash searching
    return aEntry1.iPos == aEntry2.iPos;
    }
/**
Required by RHashSet<TLookupEntry> to generate hash value.
@see    RHashSet
*/
TUint32 HashFunction(const TLookupEntry& aEntry)
    {
    return (DefaultHash::Integer(I64HIGH(aEntry.iPos)) + DefaultHash::Integer(I64LOW(aEntry.iPos)));
    }

/////////////////////////////// class CDynamicDirCache ///////////////////////////
CDynamicDirCache::~CDynamicDirCache()
    {
//  __PRINT(_L("CDynamicDirCache::~CDynamicDirCache()"));

    // we should never decommit locked pages
    while (!iLockedQ.IsEmpty())
        {
        TDynamicDirCachePage* page = iLockedQ.Last();
        DeQueue(page);      // remove from queue
        LookupTblRemove(page->StartPos());  // remove from lookuptable
        delete page;
        }
    ASSERT(iLockedQCount == 0);

    while (!iUnlockedQ.IsEmpty())
        {
        TDynamicDirCachePage* page = iUnlockedQ.Last();
        DeQueue(page);      // remove from queue
        LookupTblRemove(page->StartPos());  // remove from lookuptable
        DecommitPage(page); // inform cache client to decommit page memory
        delete page;
        }
    ASSERT(iUnlockedQCount == 0);

    delete iActivePage;

    ASSERT(iLookupTable.Count() == 0);
    iLookupTable.Close();
    if (iCacheMemoryClient)
        iCacheMemoryClient->Reset();
    }

/**
Constructor of CDynamicDirCache.
@param  aDrive  local drive interface to read/write media
@param  aMinPageNum the minimum page number for the cache, includes iActive page and locked pages.
@param  aMaxPageNum the maximum page number for the cache, includes iActive page, locked pages and unlocked pages.
@param  aPageSizeInBytesLog2    the log2 value of page size in bytes, assumes page size is always a power of two
*/
CDynamicDirCache::CDynamicDirCache(TFatDriveInterface& aDrive, TUint32 aMinPageNum, TUint32 aMaxPageNum, TUint32 aPageSizeInBytesLog2)
:iPageSizeLog2(aPageSizeInBytesLog2),
iMinSizeInPages(aMinPageNum),
iMaxSizeInPages(aMaxPageNum),
iDrive(aDrive),
iLockedQ(_FOFF(TDynamicDirCachePage, iLink)),
iUnlockedQ(_FOFF(TDynamicDirCachePage, iLink)),
iLockedQCount(0),
iUnlockedQCount(0),
iHashFunction(HashFunction),
iIdentityFunction(IdentityFunction),
iLookupTable(iHashFunction, iIdentityFunction)
    {
    iPageSizeInBytes = 1 << aPageSizeInBytesLog2;
    iCacheDisabled = EFalse;
    iMinCacheSizeInBytes = aMinPageNum << aPageSizeInBytesLog2;
    iMaxCacheSizeInBytes = aMaxPageNum << aPageSizeInBytesLog2;
    ASSERT(iPageSizeInBytes && iPageSizeInBytes <= iMinCacheSizeInBytes && iMinCacheSizeInBytes <= iMaxCacheSizeInBytes);
    // initial value, will be reset from outside
    iCacheBasePos = 0;
    }

/**
Second phase constructor of CDynamicDirCache.
@param  aClientName the identification of cache memeory client this cache connects
*/
void CDynamicDirCache::ConstructL(const TDesC& aClientName)
    {
//    __PRINT3(_L("CDynamicDirCache::ConstructL(Min=%u, Max=%u, page=%u)"), iMinCacheSizeInBytes, iMaxCacheSizeInBytes, iPageSizeInBytes);
    CCacheMemoryManager* manager = CCacheMemoryManagerFactory::CacheMemoryManager();
    if (manager)
        {
        // client will register itself onto cache memory manager when created
        // note this operation may leave under OOM condition
        iCacheMemoryClient = manager->ConnectClientL(aClientName, iMinSizeInPages * PageSizeInSegs(), iMaxSizeInPages * PageSizeInSegs());
        }
    else
        {
        User::Leave(KErrNotSupported);
        }

    ASSERT(iCacheMemoryClient);
    if (!iCacheMemoryClient)
        {
        User::Leave(KErrNoMemory);
        }

    // reserve active page
    iActivePage = AllocateAndLockNewPageL(0);
    ASSERT(iActivePage);
    if (!iActivePage)
        {
        User::Leave(KErrNoMemory);
        }
    iActivePage->SetPageType(TDynamicDirCachePage::EActivePage);
    }

/**
Static factory function of CDynamicDirCache
*/
CDynamicDirCache* CDynamicDirCache::NewL(TFatDriveInterface& aDrive, TUint32 aMinPageNum, TUint32 aMaxPageNum, TUint32 aPageSizeLog2, const TDesC& aClientName)
    {
//    __PRINT3(_L("CDynamicDirCache::NewL(MinPageNum=%u, MaxPageNum=%u, page=%u)"), aMinPageNum, aMaxPageNum, 1<<aPageSizeLog2);
    CDynamicDirCache* pSelf = new (ELeave) CDynamicDirCache(aDrive, aMinPageNum, aMaxPageNum, aPageSizeLog2);
    CleanupStack::PushL(pSelf);
    pSelf->ConstructL(aClientName);
    CleanupStack::Pop();
    return pSelf;
    }

/**
Insert an unlocked page into the last position of the locked queue, may squeeze the original last page into
the unlocked queue.
This function is used on last visited but 'unlocked' pages to avoid excessive lock/unlock calls to cache memory
manager as contiguous entry reading/writing often happens on the same page.
@param  aPage   the page to be inserted.
@pre    the page type of aPage should only be TDynamicDirCachePage::EUnknown
*/
void CDynamicDirCache::MakePageLastLocked(TDynamicDirCachePage* aPage)
    {
    // this function should not be called on active pages
    ASSERT(aPage->iType == TDynamicDirCachePage::EUnknown);

    if (iLockedQ.IsEmpty())
        {
        // if locked queue is empty, add it onto the locked queue directly
        AddFirstOntoQueue(aPage, TDynamicDirCachePage::ELocked);
        }
    else
        {
        // otherwise, we squeeze for the last position on locked queue
        while (iLockedQCount + 1 >= iMinSizeInPages)
            {
            TDynamicDirCachePage* last = iLockedQ.Last();
            DeQueue(last);
            UnlockPage(last);
            AddFirstOntoQueue(last, TDynamicDirCachePage::EUnlocked);
            }

        // iLockedQCount + 1 < iMinSizeInPages
        iLockedQ.AddLast(*aPage);
        aPage->SetPageType(TDynamicDirCachePage::ELocked);
        iLockedQCount++;
        }
    }

/**
Read data from a single page. If the page is not found or not valid anymore, read media onto iActive page
first.
@param  aPos    the starting position of the media address to be read.
@param  aLength the length of the content to be read.
@param  aDes    the descriptor to contain the content.
@pre    aLength should be no more than page size.
*/
void CDynamicDirCache::ReadDataFromSinglePageL(TInt64 aPos, TInt aLength, TDes8& aDes)
    {
    //-- the data section is in the cache page entirely, take data directly from the cache
    TDynamicDirCachePage* pPage = FindPageByPos(aPos);
    if (pPage)
        {
        // lock page before reading,
        if (LockPage(pPage) != NULL)
            {
            // read data
            aDes.Copy(pPage->PtrInPage(aPos), aLength);

            // if page is from unlocked queue, insert it onto the last page of the locked
            //  queue. this is to avoid excessive locking and unlocking operations that is
            //  highly likely to happen for following reads.
            if (pPage->PageType() == TDynamicDirCachePage::EUnlocked)
                {
                DeQueue(pPage);
                MakePageLastLocked(pPage);
                }
            }
        else    // page locking failed
            {
            ASSERT(pPage->PageType() == TDynamicDirCachePage::EUnlocked);
            DeQueue(pPage);
            LookupTblRemove(pPage->StartPos());
            DecommitPage(pPage);
            delete pPage;
            pPage = NULL;
            }
        }

    if (!pPage)
        {
        // if page not found or page data not valid anymore, use active page to read data in
        pPage = UpdateActivePageL(aPos);
        // read data
        aDes.Copy(pPage->PtrInPage(aPos), aLength);
        }

    }

//====================================================================
/**
Implementation of pure virtual function.
@see    MWTCacheInterface::ReadL()
*/
void CDynamicDirCache::ReadL(TInt64 aPos, TInt aLength, TDes8& aDes)
    {
#ifdef _DEBUG
    if(iCacheDisabled)
        {
        // cache is disabled for debug purposes
        __PRINT(_L("CDynamicDirCache disabled"));
        User::LeaveIfError(iDrive.ReadNonCritical(aPos, aLength, aDes));
        return;
        }
#endif //_DEBUG

    aDes.Zero();
    const TUint32 PageSz = iPageSizeInBytes;//-- cache page size

    TInt64 pageStartMedPos = CalcPageStartPos(aPos);
    const TUint32 bytesToPageEnd = (TUint32)(pageStartMedPos + PageSz - aPos); //-- number of bytes from aPos to the end of the page

//    __PRINT5(_L("CDynamicDirCache::ReadL: aPos=%lx, aLength=%x, page:%lx, pageSz:%x, bytesToPageEnd=%x"), aPos, aLength, pageStartMedPos, PageSz, bytesToPageEnd);
    // if all data needed is on a single page
    if((TUint32)aLength <= bytesToPageEnd)
        {
        ReadDataFromSinglePageL(aPos, aLength, aDes);
        }
    // or data to be read cross cache page boundary or probably we have more than 1 page to read
    else
        {
        __PRINT(_L("CDynamicDirCache::ReadL() CROSS PAGE!"));
        TUint32 dataLen(aLength);   //-- current data length
        TInt64  currMediaPos(aPos); //-- current media position

        //-- 1. read data that are already in the current page
        ReadDataFromSinglePageL(currMediaPos, bytesToPageEnd, aDes);
        dataLen -= bytesToPageEnd;
        currMediaPos += bytesToPageEnd;

        TPtr8 dataNext = aDes.MidTPtr(aDes.Length());

        //-- 2. read whole pages of data
        while (dataLen >= PageSz)
            {
            //-- find out if currMediaPos is in cache. If not, find a spare page and read data there
            ReadDataFromSinglePageL(currMediaPos, PageSz, dataNext);
            currMediaPos += PageSz;
            dataLen -= PageSz;
            dataNext = dataNext.MidTPtr(dataNext.Length());
            }

        //-- 3. read the rest of the data
        if(dataLen > 0)
            {
            ReadDataFromSinglePageL(currMediaPos, dataLen, dataNext);
            }
        } //else((TUint32)aLength <= bytesToPageEnd)
    }

/**
Write data through a single page. If the page is not found or not valid anymore, read media onto iActive page
first, then write data through iActive page.
@param  aPos    the starting position of the media address to be write.
@param  aData   the starting address that the writing content lives in the ram.
@param  aDataLen    the length of the content to be written.
@pre    aDataLen    should be no more than page size.
*/
void CDynamicDirCache::WriteDataOntoSinglePageL(TInt64 aPos, const TUint8* aData, TUint32 aDataLen)
    {
    ASSERT(aDataLen <= iPageSizeInBytes);
    //-- the data section is in the cache page entirely, take data directly from the cache
    TDynamicDirCachePage* pPage = FindPageByPos(aPos);
    if (pPage)
        {
        // lock page before writing,
        if (LockPage(pPage) != NULL)
            {
            //-- update cache
            Mem::Copy(pPage->PtrInPage(aPos), aData, aDataLen);
            }
        else
            {
            ASSERT(pPage->PageType() == TDynamicDirCachePage::EUnlocked);
            DeQueue(pPage);
            LookupTblRemove(pPage->StartPos());
            DecommitPage(pPage);
            delete pPage;
            pPage = NULL;
            }
        }

    // if page not found or page data not valid anymore, use active page to read data in
    if (!pPage)
        {
        pPage = UpdateActivePageL(aPos);
        //-- update cache
        Mem::Copy(pPage->PtrInPage(aPos), aData, aDataLen);
        }

    // make sure the page is unlocked after use
    if (pPage->PageType() == TDynamicDirCachePage::EUnlocked)
        {
        UnlockPage(pPage);
        }

    // always make writting events MRU
    MakePageMRU(aPos);
    return;
    }

/**
Implementation of pure virtual function.
@see    MWTCacheInterface::WriteL()
*/
void CDynamicDirCache::WriteL(TInt64 aPos,const TDesC8& aDes)
    {
#ifdef _DEBUG
    if(iCacheDisabled)
        {
        // cache is disabled for debug purposes
        __PRINT(_L("CDynamicDirCache disabled"));
        User::LeaveIfError(iDrive.WriteCritical(aPos,aDes));
        return;
        }
#endif //_DEBUG

    TUint32 dataLen = aDes.Size();
    const TUint8* pData   = aDes.Ptr();
    const TUint32 PageSz  = iPageSizeInBytes; //-- cache page size

    TInt64 pageStartMedPos = CalcPageStartPos(aPos);
    TUint32 bytesToPageEnd = (TUint32)(pageStartMedPos + PageSz - aPos);

//    __PRINT5(_L("CDynamicDirCache::WriteL: aPos=%lx, aLength=%x, page:%lx, pageSz:%x, bytesToPageEnd=%x"), aPos, dataLen, pageStartMedPos, PageSz, bytesToPageEnd);

    if(dataLen <= bytesToPageEnd)
        {
        WriteDataOntoSinglePageL(aPos, pData, dataLen);
        }
    else
        {
        __PRINT(_L("CDynamicDirCache::WriteL() CROSS PAGE!"));

        //-- Data to be written cross cache page boundary or probably we have more than 1 page to write
        TInt64  currMediaPos(aPos);

        //-- 1. update the current page
        WriteDataOntoSinglePageL(currMediaPos, pData, bytesToPageEnd);

        pData += bytesToPageEnd;
        currMediaPos += bytesToPageEnd;
        dataLen -= bytesToPageEnd;

        //-- 2. write whole pages of data to the cache
        while (dataLen >= PageSz)
            {
            WriteDataOntoSinglePageL(currMediaPos, pData, PageSz);

            pData += PageSz;
            currMediaPos += PageSz;
            dataLen -= PageSz;
            }

        //-- 3. write the rest of the data
        if(dataLen > 0)
            {
            WriteDataOntoSinglePageL(currMediaPos, pData, dataLen);
            }
        }// else(dataLen <= bytesToPageEnd)


    //-- write data to the media
    const TInt nErr = iDrive.WriteCritical(aPos,aDes);
    if(nErr != KErrNone)
        {//-- some serious problem occured during writing, invalidate cache.
        InvalidateCache();
        User::Leave(nErr);
        }
    }

/**
Implementation of pure virtual function.
@see    MWTCacheInterface::InvalidateCache()
*/
void CDynamicDirCache::InvalidateCache(void)
    {
    __PRINT2(_L("CDynamicDirCache::InvalidateCache(locked=%d, unlocked=%d)"), iLockedQCount, iUnlockedQCount);
    // we should never decommit locked pages as they needs to be reserved anyway
    // the overhead of unnecessary page committing operations
    while(!iLockedQ.IsEmpty())
        {
        TDynamicDirCachePage* page = iLockedQ.Last();
        DeQueue(page);                      // remove from queue
        LookupTblRemove(page->StartPos());  // remove from lookuptable
        delete page;
        }
    ASSERT(iLockedQCount == 0);

    // however we should decommit unlocked pages here
    while (!iUnlockedQ.IsEmpty())
        {
        TDynamicDirCachePage* page = iUnlockedQ.Last();
        DeQueue(page);                      // remove from queue
        LookupTblRemove(page->StartPos());  // remove from lookuptable
        DecommitPage(page);                 // inform cache client to decommit page memory
        delete page;
        }
    ASSERT(iUnlockedQCount == 0);

    ASSERT(iLookupTable.Count() == 0);
    iLookupTable.Close();

    ASSERT(iCacheMemoryClient);

    // initialize cache state.
    // Note that once the client is reset, all pages lose connection with the client
    //  including the active page. So we will need to reset and re-allocate active page
    //  properly.
    if (iCacheMemoryClient)
        iCacheMemoryClient->Reset();

    // reset and re-allocate active page
    ResetPagePos(iActivePage);              // reset start media position (0), invalidate page content
    TUint8* startRamAddr = iCacheMemoryClient->AllocateAndLockSegments(PageSizeInSegs());
    // this should always succeed as the client has just been reset and there are always reserved pages
    ASSERT(startRamAddr);
    iActivePage->SetStartPtr(startRamAddr); // set RAM address
    }


/** this method isn't implemented*/
void CDynamicDirCache::InvalidateCachePage(TUint64 /*aPos*/)
    {
    ASSERT(0);
    }


/**
Implementation of pure virtual function.
@see    MWTCacheInterface::PosCached()
*/
TUint32 CDynamicDirCache::PosCached(const TInt64& aPos, TInt64& aCachedPosStart)
    {
    const TInt64 pageStartMedPos = CalcPageStartPos(aPos);

    // only search the page in lookup table
    // NOTE: we don't count the active page into acount here,
    // this is to avoid pulling next pages recursively
    TDynamicDirCachePage* pPage = LookupTblFind(pageStartMedPos);

    // then check if page is still valid if page is on Unlocked Page Queue
    if (pPage && pPage->PageType() == TDynamicDirCachePage::EUnlocked)
        {
        if (LockPage(pPage) != NULL)
            {
//          __PRINT1(_L("CDynamicDirCache::PosCached: page(0x%lx) found on Unlocked Queue!"), aPos);
            // have to unlock it before returning, otherwise there will be memory leak
            UnlockPage(pPage);
            aCachedPosStart = pPage->StartPos();
            return pPage->PageSizeInBytes();
            }
        else    // if the unlocked page is not valid anymore, remove it
            {
            DeQueue(pPage);
            LookupTblRemove(pPage->StartPos());
            DecommitPage(pPage);
            delete pPage;
            pPage = NULL;
            }
        }
    // otherwise if page is already locked or valid active page
    else if (pPage)
        {
        __PRINT1(_L("CDynamicDirCache::PosCached: page(0x%lx) on Locked Queue!"), aPos);
        aCachedPosStart = pPage->StartPos();
        return pPage->PageSizeInBytes();
        }

    // page is not found or not valid anymore
    return 0;
    }

/**
Implementation of pure virtual function.
@see    MWTCacheInterface::CacheSizeInBytes()
*/
TUint32 CDynamicDirCache::CacheSizeInBytes()  const
    {
    return iMaxCacheSizeInBytes;
    }

/**
Implementation of pure virtual function.
@see    MWTCacheInterface::Control()
*/
TInt CDynamicDirCache::Control(TUint32 aFunction, TUint32 aParam1, TAny* aParam2)
    {
    TInt r = KErrNotSupported;
#ifdef _DEBUG
    (void)aParam2;
    switch(aFunction)
        {
        // disable / enable cache, for debug
        // if aParam1 != 0 cache will be disabled, enabled otherwise
        case EDisableCache:
            iCacheDisabled = aParam1 ? 1 : 0;
            r = KErrNone;
        break;

        // dump cache, for debug
        case EDumpCache:
            {
            RFs fs;
            fs.Connect();
            const TUint32 debugRegister = DebugRegister();
            fs.SetDebugRegister(debugRegister|KFSYS);
            Dump();
            fs.SetDebugRegister(debugRegister);
            fs.Close();
            break;
            }
        case ECacheInfo:
            {
            RFs fs;
            fs.Connect();
            const TUint32 debugRegister = DebugRegister();
            fs.SetDebugRegister(debugRegister|KFSYS);
            Info();
            fs.SetDebugRegister(debugRegister);
            fs.Close();
            break;
            }

        default:
            __PRINT1(_L("CDynamicDirCache::Control() invalid function: %d"), aFunction);
            ASSERT(0);
        break;
        }

#else
    (void)aFunction; //-- supress warnings
    (void)aParam1;
    (void)aParam2;
    User::Invariant(); //-- don't call this method in release build
#endif //_DEBUG

    return r;
    }

/**
Implementation of pure virtual function.
@see    MWTCacheInterface::SetCacheBasePos()
*/
void CDynamicDirCache::SetCacheBasePos(TInt64 aBasePos)
    {
    iCacheBasePos = aBasePos;
    }

/**
Implementation of pure virtual function.
@see    MWTCacheInterface::SetCacheBasePos()
*/
TUint32 CDynamicDirCache::PageSizeInBytesLog2() const
    {
    return iPageSizeLog2;
    }

/**
Implementation of pure virtual function.
@see    MWTCacheInterface::MakePageMRU()
*/
void CDynamicDirCache::MakePageMRU(TInt64 aPos)
    {
    __PRINT1(_L("MakePageMRU (%lx)"), aPos);
//  __PRINT4(_L("Current Cache State: iLockedQCount=%d, iUnlockedQCount=%d, iLookupTbl=%d, iMaxSizeInPages=%d"), iLockedQCount, iUnlockedQCount, iLookupTable.Count(), iMaxSizeInPages);
    // check the MRU page first, if it is already the MRU page, we can return immediately
    TInt64 pageStartMedPos = CalcPageStartPos(aPos);
    if (!iLockedQ.IsEmpty())
        {
        if (iLockedQ.First()->StartPos() == pageStartMedPos)
            {
            return;
            }
        }

    TDynamicDirCachePage* pPage = FindPageByPos(aPos);
    if (pPage)
        {
        ASSERT(pPage->IsValid());
        // lock page before make it MRU
        if (pPage->PageType() == TDynamicDirCachePage::EUnlocked)
            {
            ASSERT(!pPage->IsLocked());
            if (LockPage(pPage) == NULL)
                {
                DeQueue(pPage);
                LookupTblRemove(pPage->StartPos());
                DecommitPage(pPage);
                delete pPage;
                pPage = NULL;
                }
            }
        else
            {
            // error checking: page should either be locked or active
            ASSERT(LockPage(pPage) != NULL);
            }
        }

    // if page not found or page data not valid anymore, use active page to read data
    if (!pPage)
        {
        TRAPD(err, pPage = UpdateActivePageL(aPos));
        if (err != KErrNone)
            {
            // problem occurred reading active page, return immediately.
            return;
            }
        }

    // by now, the page is either locked or active page
    ASSERT(pPage && pPage->IsValid() && pPage->IsLocked());

    switch (pPage->PageType())
        {
        // if the page is the active page, we will need to find a new active page for replacement
        case TDynamicDirCachePage::EActivePage:
            {
            TDynamicDirCachePage* newAP = NULL;
            // if there is more cache room available, try to create a new page first
            if (!CacheIsFull())
                {
                // allocate and lock a new page
                TRAPD(err, newAP = AllocateAndLockNewPageL(0));
                // if any error ocurrs, return immediately
                if (err != KErrNone)
                    {
                    // unlock the page that was originally unlocked before leave
                    if (pPage->PageType() == TDynamicDirCachePage::EUnlocked)
                        {
                        UnlockPage(pPage);
                        }
                    return;
                    }

                if (newAP)
                    {
                    // replace the active page with the new page
                    newAP->SetPageType(TDynamicDirCachePage::EActivePage);
                    iActivePage = newAP;
                    }
                }

            // if cache has grown to its max size, or new page allocation failed
            if (!newAP)
                {
                // try to lock the LRU page on the unlocked page queque first
                if (!iUnlockedQ.IsEmpty())
                    {
                    newAP = iUnlockedQ.Last();
                    ASSERT(newAP->IsValid());
                    if (LockPage(newAP) != NULL)
                        {
                        // deque, reset pos, set new type
                        DeQueue(newAP);
                        LookupTblRemove(newAP->StartPos());
                        ResetPagePos(newAP);
                        newAP->SetPageType(TDynamicDirCachePage::EActivePage);
                        // replace active page
                        iActivePage = newAP;
                        }
                    // if falied locking the LRU page from unclocked queque,
                    // delete it
                    else
                        {
                        DeQueue(newAP);
                        LookupTblRemove(newAP->StartPos());
                        DecommitPage(newAP);
                        delete newAP;
                        newAP = NULL;
                        }
                    }
                }

            // if still have not found new active page
            // grab the LRU page from Locked Page Queue for active page
            if (!newAP)
                {
                ASSERT(!iLockedQ.IsEmpty());
                newAP = iLockedQ.Last();
                // deque, reset pos, set new type
                DeQueue(newAP);
                LookupTblRemove(newAP->StartPos());
                ResetPagePos(newAP);
                newAP->SetPageType(TDynamicDirCachePage::EActivePage);
                // replace active page
                iActivePage = newAP;
                }

            // we should always be able to find a locked page for active page
            ASSERT(newAP != NULL);

            // make original page (i.e. former active page) MRU
            // add onto locked queue
            AddFirstOntoQueue(pPage, TDynamicDirCachePage::ELocked);
            // add onto lookuptbl, as active page is not on lookup tbl originally
            LookupTblAdd(pPage);
            // check cache limit
            CheckThresholds();
            return;
            }
        case TDynamicDirCachePage::EUnlocked:
            {
            // if page was originally on Unlocked Page Queque, remove it from Unlocked Page Queue, add it
            // to the Locked Page Queue and make it MRU
            DeQueue(pPage);
            AddFirstOntoQueue(pPage, TDynamicDirCachePage::ELocked);
            // check cache limit
            CheckThresholds();
            return;
            }
        case TDynamicDirCachePage::ELocked:
            {
            // otherwise the page was on Locked Page Queue, make it MRU
            // no need to check cache limit
            if (pPage != iLockedQ.First())
                {
                DeQueue(pPage);
                AddFirstOntoQueue(pPage, TDynamicDirCachePage::ELocked);
                return;
                }
            break;
            }
        default:
            ASSERT(0);
        }
    }

//====================================================================
/**
Internal query function, to check if aPos is cached or not. iActive page is included in searching.
*/
TDynamicDirCachePage* CDynamicDirCache::FindPageByPos(TInt64 aPos)
    {
    __PRINT1(_L("CDynamicDirCache::FindPageByPos(aPos=%lx)"), aPos);
    // align the page position
    TInt64 pageStartMedPos = CalcPageStartPos(aPos);

    if ((iActivePage->StartPos() == pageStartMedPos))
        {
        ASSERT(iActivePage->IsValid());
        return iActivePage;
        }

    // search in lookup table
    return LookupTblFind(pageStartMedPos);
    }

/**
read a page length data into iActive page and return iActive page if read is successful.
*/
TDynamicDirCachePage* CDynamicDirCache::UpdateActivePageL(TInt64 aPos)
    {
    // align the page position
    TInt64 pageStartMedPos = CalcPageStartPos(aPos);

    if (iActivePage->StartPos() == pageStartMedPos && iActivePage->IsValid())
        {
        return iActivePage;
        }

    __PRINT2(_L("CDynamicDirCache::UpdateActivePageL(aPos=%lx, active=%lx)"), aPos, iActivePage->StartPos());

    // set start med pos value, no other effects, only available to active page
    iActivePage->SetPos(pageStartMedPos);

    // read data, make active page valid
    TUint8* data = iActivePage->PtrInPage(iActivePage->iStartMedPos);
    TPtr8 dataPtr(data, iPageSizeInBytes);
    const TInt nErr = iDrive.ReadNonCritical(iActivePage->iStartMedPos, iPageSizeInBytes, dataPtr);
    if(nErr !=KErrNone)
        {
        // some serious problem occured during reading, invalidate cache.
        InvalidateCache();
        User::Leave(nErr);
        }
    iActivePage->SetValid(ETrue);

    return iActivePage;
    }

/**
Check if the number of (locked pages + iActive page) and unlocked pages have exceeded minimum allowed page
number and maximum allowed page number respectively.
*/
void CDynamicDirCache::CheckThresholds()
    {
    while (iLockedQCount + 1 > iMinSizeInPages)
        {
        TDynamicDirCachePage* movePage = iLockedQ.Last();
        UnlockPage(movePage);
        DeQueue(movePage);
        TInt err = LookupTblRemove(movePage->StartPos());
        ASSERT(err == KErrNone);

        // if it is a valid page, add onto unlocked queue
        if (movePage->StartPos() != 0)
            {
            ASSERT(movePage->IsValid());
            AddFirstOntoQueue(movePage, TDynamicDirCachePage::EUnlocked);
            err = LookupTblAdd(movePage);
            ASSERT(err == KErrNone);
            }
        else // reserved page, delete
            {
            DecommitPage(movePage);
            delete movePage;
            }
        }

    // if unlocked queue exceeds limit, delete LRU page
    // note: all pages on unlocked queue should be valid
    while (iUnlockedQCount > iMaxSizeInPages - iMinSizeInPages)
        {
        TDynamicDirCachePage* removePage = iUnlockedQ.Last();
        ASSERT(removePage->StartPos() != 0 && removePage->IsValid());
        DeQueue(removePage);
        LookupTblRemove(removePage->StartPos());
        DecommitPage(removePage);
        delete removePage;
        }
    }

/**
Try to create a new page and lock the page content when it is created. This function should only be called
when creating iActive page or making a page MRU (which might result in page evictions).
@return the pointer of the newly created page, or NULL if allocation failed.
@param  aStartMedPos    the starting media address of the page to be created.
@pre    aStartMedPos should not already be existing in the cache.
*/
TDynamicDirCachePage* CDynamicDirCache::AllocateAndLockNewPageL(TInt64 aStartMedPos)
    {
    __PRINT1(_L("CDynamicDirCache::AllocateAndLockNewPageL(aStartMedPos=%lx)"), aStartMedPos);

    TUint8* startRamAddr = iCacheMemoryClient->AllocateAndLockSegments(PageSizeInSegs());
    if (startRamAddr)
        {
        // create new page and return
        TDynamicDirCachePage* pPage = TDynamicDirCachePage::NewL(this, aStartMedPos, startRamAddr);
        pPage->SetLocked(ETrue);
        pPage->SetValid(EFalse);
        return pPage;
        }

    return NULL;
    }

#ifdef _DEBUG
/**
Dump cache information, only enabled in debug mode.
@see CDynamicDirCache::Control()
*/
void CDynamicDirCache::Info() const
    {
    __PRINT(_L("======== CDynamicDirCache::Info ========="));
    const TUint32 SegmentSizeInBytesLog2 = CCacheMemoryManagerFactory::CacheMemoryManager()->SegmentSizeInBytesLog2();
    // page size
    __PRINT1(_L("=== Pages size:               [%d Bytes]"), iPageSizeInBytes);
    __PRINT1(_L("=== Segment size:             [%d Bytes]"), 1 << SegmentSizeInBytesLog2);

    // data size:
    __PRINT1(_L("=== Min data size:            [%d Bytes]"), iMinSizeInPages << iPageSizeLog2);
    __PRINT1(_L("=== Max data size:            [%d Bytes]"), iMaxSizeInPages << iPageSizeLog2);

    // memory size:
    const TUint32 pageMemSizeLog2 = iPageSizeLog2 > SegmentSizeInBytesLog2 ? iPageSizeLog2 : SegmentSizeInBytesLog2;
    __PRINT1(_L("=== Min memory size:          [%d Bytes]"), iMinSizeInPages << pageMemSizeLog2);
    __PRINT1(_L("=== Max memory size:          [%d Bytes]"), iMaxSizeInPages << pageMemSizeLog2);

    // reserved pages
    __PRINT1(_L("=== Number of pages reserved: [%d]"), iMinSizeInPages);
    __PRINT1(_L("=== Reserved memory:          [%d Bytes]"), (iMinSizeInPages * PageSizeInSegs()) << SegmentSizeInBytesLog2);
    // locked page num
    __PRINT1(_L("=== Number of pages locked:   [%d]"), iLockedQCount);
    __PRINT1(_L("=== Locked memory:            [%d Bytes]"), (iLockedQCount * PageSizeInSegs()) << SegmentSizeInBytesLog2);
    // unlocked page num
    __PRINT1(_L("=== Number of pages unlocked: [%d]"), iUnlockedQCount);
    __PRINT1(_L("=== Unlocked memory:          [%d Bytes]"), (iUnlockedQCount * PageSizeInSegs()) << SegmentSizeInBytesLog2);
    }

/**
Dump cache content, only enabled in debug mode.
@see CDynamicDirCache::Control()
*/
void CDynamicDirCache::Dump()
    {
    __PRINT(_L("======== CDynamicDirCache::Dump ========="));
    if (!iLockedQ.IsEmpty())
        {
        TDblQueIter<TDynamicDirCachePage> q(iLockedQ);
        q.SetToFirst();
        TInt i = 0;
        while((TDynamicDirCachePage*)q)
            {
            TDynamicDirCachePage* pP = q++;
            __PRINT3(_L("=== CDynamicDirCache::iLockedQ\t[%4d](pos=%lx, size=%d)"), i++, pP->StartPos(), pP->PageSizeInBytes());
            }
        }
    if (!iUnlockedQ.IsEmpty())
        {
        TDblQueIter<TDynamicDirCachePage> q(iUnlockedQ);
        q.SetToFirst();
        TInt i = 0;
        while((TDynamicDirCachePage*)q)
            {
            TDynamicDirCachePage* pP = q++;
            __PRINT3(_L("=== CDynamicDirCache::iUnlockedQ\t[%4d](pos=%lx, size=%u)"), i++, pP->StartPos(), pP->PageSizeInBytes());
            }
        }
    __PRINT2(_L("=== CDynamicDirCache::iActivePage\t[*](pos=%lx, size=%u)"), iActivePage->StartPos(), iActivePage->PageSizeInBytes());

    if (iLookupTable.Count())
        {
        TInt i = 0;
        THashSetIter<TLookupEntry> iter(iLookupTable);
        TLookupEntry* pEntry;
        pEntry = (TLookupEntry*) iter.Next();
        while(pEntry)
            {
            TDynamicDirCachePage* pP = pEntry->iPage;
            __PRINT3(_L("=== CDynamicDirCache::iLookupTable\t[%4d](pos=%lx, size=%u)"), i++, pP->StartPos(), pP->PageSizeInBytes());
            pEntry = (TLookupEntry*) iter.Next();
            };
        }
    __PRINT(_L("===========================================\n"));
    }
#endif //_DEBUG

/**
Lock an unlocked page, or do nothing if the page is already locked.
@return TUint8* pointer of the page to be locked, if locking is successful, otherwise return NULL.
@param  aPage   the pointer of the page to be locked.
*/
TUint8* CDynamicDirCache::LockPage(TDynamicDirCachePage* aPage)
    {
    ASSERT(aPage != NULL);
    if (aPage->IsLocked())
        return aPage->StartPtr();

    TInt r = iCacheMemoryClient->LockSegments(aPage->StartPtr(), PageSizeInSegs());
    if (r == KErrNone)
        {
        aPage->SetLocked(ETrue);
        return aPage->StartPtr();
        }

    return NULL;
    }

/**
Unlock a locked page.
@return TInt    KErrNone if unlocking was successful, otherwise system-wide error code.
@param  aPage   the pointer of the page to be unlocked.
*/
TInt CDynamicDirCache::UnlockPage(TDynamicDirCachePage* aPage)
    {
    ASSERT(aPage != NULL);
    __PRINT1(_L("CDynamicDirCache::UnlockPage(%lx)"), aPage->StartPos());
    TInt r = iCacheMemoryClient->UnlockSegments(aPage->StartPtr(), PageSizeInSegs());
    if (r == KErrNone)
        {
        aPage->SetLocked(EFalse);
        }
    return r;
    }

/**
Decommit a locked or unlocked page.
@return TInt    KErrNone if decommition was successful, otherwise system-wide error code.
@param  aPage   the pointer of the page to be decommitted.
*/
TInt CDynamicDirCache::DecommitPage(TDynamicDirCachePage* aPage)
    {
    ASSERT(aPage != NULL);
    __PRINT1(_L("CDynamicDirCache::DecommitPage(%lx)"), aPage->StartPos());
    if (aPage)
        {
        TInt r = iCacheMemoryClient->DecommitSegments(aPage->StartPtr(), PageSizeInSegs());
        if (r == KErrNone)
            {
            aPage->SetLocked(EFalse);
            aPage->SetValid(EFalse);
            }
        return r;
        }
    return KErrArgument;
    }

/////////////////////////// aluxiliary functions //////////////////////////////////
/**
Calculate the page size in segments. Segment size is the size of the kernel memory unit that cache memory manager manages.
We are making assumption here about the page size: page size should always be either less than segment size
or multiple times of segment size
@return TUint32 the page size in segments.
*/
TUint32 CDynamicDirCache::PageSizeInSegs() const
    {
    // initialize cache memory manager as all file systems have mounted by now
    ASSERT(CCacheMemoryManagerFactory::CacheMemoryManager());
    const TUint32 SegmentSizeInBytesLog2 = CCacheMemoryManagerFactory::CacheMemoryManager()->SegmentSizeInBytesLog2();

    // Page size should be non-zero
    ASSERT(iPageSizeInBytes);

    TUint32 segs = iPageSizeInBytes >> SegmentSizeInBytesLog2;
    return segs > 0 ? segs : 1;
    }

/**
Deque the page from locked queue or unlocked queue. All pages are managed through these two queues, expect iActive
page.
@param  aPage   the pointer of the page to be dequeued
@return TInt    KErrArgument if aPage is invalid, otherwise KErrNone.
*/
TInt CDynamicDirCache::DeQueue(TDynamicDirCachePage* aPage)
    {
    ASSERT(aPage);
    if (!aPage)
        return KErrArgument;

    if (aPage->iType == TDynamicDirCachePage::ELocked)
        {
        aPage->Deque();
        aPage->SetPageType(TDynamicDirCachePage::EUnknown);
        --iLockedQCount;
        }
    else if (aPage->iType == TDynamicDirCachePage::EUnlocked)
        {
        aPage->Deque();
        aPage->SetPageType(TDynamicDirCachePage::EUnknown);
        --iUnlockedQCount;
        }
    else
        {
        ASSERT(0);
        return KErrArgument;
        }
    return KErrNone;
    }

/**
Insert a page to the first position of locked queue or unlocked queue.
@param  aPage   the pointer of the page to be inserted.
@param  aType   the type of the queue to be inserted.
@return TInt    KErrArgument if aPage is invalid, otherwise KErrNone.
*/
TInt CDynamicDirCache::AddFirstOntoQueue(TDynamicDirCachePage* aPage, TDynamicDirCachePage::TPageType aType)
    {
    ASSERT(aPage);
    if (!aPage)
        return KErrArgument;

    // page must be dequed first or it is active page
    if (aPage->iType != TDynamicDirCachePage::EActivePage && aPage->iType != TDynamicDirCachePage::EUnknown)
        {
        ASSERT(0);
        return KErrArgument;
        }

    if (aType == TDynamicDirCachePage::ELocked)
        {
        iLockedQ.AddFirst(*aPage);
        aPage->SetPageType(TDynamicDirCachePage::ELocked);
        ++iLockedQCount;
        }
    else if (aType == TDynamicDirCachePage::EUnlocked)
        {
        iUnlockedQ.AddFirst(*aPage);
        aPage->SetPageType(TDynamicDirCachePage::EUnlocked);
        ++iUnlockedQCount;
        }
    else
        {
        ASSERT(0);
        return KErrArgument;
        }

    return KErrNone;
    }

/**
Remove a page from the lookup table, indexed by the starting media address of the page content.
@param  aPagePos    the starting media position of the page to be removed.
*/
TInt CDynamicDirCache::LookupTblRemove(TInt64 aPagePos)
    {
    if (aPagePos == 0)
        {
        return KErrNone;
        }

    TInt r = iLookupTable.Remove(TLookupEntry(aPagePos, 0, NULL));
    return r;
    }

/**
Insert a page to the lookup table, indexed by the starting media address of the page content.
@param  aPagePos    the starting media position of the page to be inserted.
*/
TInt CDynamicDirCache::LookupTblAdd(TDynamicDirCachePage* aPage)
    {
    ASSERT(aPage);
    if (!aPage)
        return KErrArgument;

    if (aPage->StartPos() == 0)
        {
        return KErrNone;
        }

    TInt r = iLookupTable.Insert(TLookupEntry(aPage->StartPos(), iPageSizeInBytes, aPage));
    return r;
    }

/**
Reset the media address of the page to 0, also invalidate the page.
@param  aPage   the pointer of the page to be reset.
*/
TInt CDynamicDirCache::ResetPagePos(TDynamicDirCachePage* aPage)
    {
    ASSERT(aPage);
    if (!aPage)
        return KErrArgument;

    aPage->ResetPos();
    return KErrNone;
    }

/**
Search the lookup table to find the page start with a specific media address.
@param  aPos    the starting media address to be searched.
*/
TDynamicDirCachePage* CDynamicDirCache::LookupTblFind(TInt64 aPos)
    {
    if (aPos == 0)
        {
        ASSERT(0);
        return NULL;
        }

    TLookupEntry* entry = iLookupTable.Find(TLookupEntry(aPos, 0, NULL));
    if(entry)
        {
        ASSERT(entry->iPage->IsValid());
        return entry->iPage;
        }

    return NULL;
    }
