//+----------------------------------------------------------------------------
//
//      File:       PbkCache.cpp
//
//      Module:     Common pbk parser
//
//      Synopsis:   Caches parsed pbk files to improve performance.  Through 
//                  XP, we would re-load and re-parse the phonebook file 
//                  every time a RAS API is called.  Really, we need to 
//                  reload the file only when the file on disk changes or when
//                  a new device is introduced to the system.
//
//      Copyright   (c) 2000-2001 Microsoft Corporation
//
//      Author:     11/03/01 Paul Mayfield
//
//+----------------------------------------------------------------------------

#ifdef  _PBK_CACHE_


extern "C" 
{
#include <windows.h>  // Win32 core
#include <pbkp.h>
#include <nouiutil.h>
#include <dtl.h>
}

#define PBK_CACHE_MAX_RASFILES 500              // Should match MAX_RASFILES
#define PBK_CACHE_INVALID_SLOT (PBK_CACHE_MAX_RASFILES + 1)

//+----------------------------------------------------------------------------
//
// Synopsis: A node in the pbk cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
class PbkCacheNode
{
public:
    PbkCacheNode();
    ~PbkCacheNode();

    VOID Close();
    DWORD SetFileName(IN WCHAR* pszFileName);

    WCHAR* GetPath() {return m_pbFile.pszPath;}
    FILETIME GetReadTime() {return m_ftRead;}
    DWORD GetLastWriteTime(FILETIME* pTime);

    DWORD Reload();

    DTLLIST* GetEntryList() {return m_pbFile.pdtllistEntries;}
    
private:
    PBFILE m_pbFile;
    FILETIME m_ftRead;
    PWCHAR m_pszFileName;
};

//+----------------------------------------------------------------------------
//
// Synopsis: The phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
class PbkCache
{
public:
    PbkCache();
    ~PbkCache();

    DWORD Initialize();

    DWORD
    GetEntry(
        IN WCHAR* pszPhonebook,
        IN WCHAR* pszEntry,
        OUT DTLNODE** ppEntryNode);

    VOID
    Lock() {EnterCriticalSection(&m_csLock);}

    VOID
    Unlock() {LeaveCriticalSection(&m_csLock);}
        

private:
    PbkCacheNode* m_Files[PBK_CACHE_MAX_RASFILES];
    CRITICAL_SECTION m_csLock;

    DWORD
    InsertNewNode(
        IN PWCHAR pszPhonebook,
        IN DWORD dwSlot,
        OUT PbkCacheNode** ppNode);

    VOID
    FindFile(
        IN WCHAR* pszFileName,
        OUT PbkCacheNode** ppNode,
        OUT DWORD* pdwIndex);
    
};

static PbkCache* g_pPbkCache = NULL;

//+----------------------------------------------------------------------------
//
// Synopsis: Instantiates the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
PbkCacheNode::PbkCacheNode() : m_pszFileName(NULL)
{
    ZeroMemory(&m_pbFile, sizeof(m_pbFile));
    ZeroMemory(&m_ftRead, sizeof(m_ftRead));
    m_pbFile.hrasfile = -1;
}

//+----------------------------------------------------------------------------
//
// Synopsis: Instantiates the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
PbkCacheNode::~PbkCacheNode()
{
    Close();
    SetFileName(NULL);
}

//+----------------------------------------------------------------------------
//
// Synopsis: Closes the file referred to by this cache node
//
// Created: pmay
//
//-----------------------------------------------------------------------------
VOID
PbkCacheNode::Close()
{
    if (m_pbFile.hrasfile != -1)
    {
        ClosePhonebookFile(&m_pbFile);
    }

    ZeroMemory(&m_pbFile, sizeof(m_pbFile));
    m_pbFile.hrasfile = -1;
    ZeroMemory(&m_ftRead, sizeof(m_ftRead));
}

//+----------------------------------------------------------------------------
//
// Synopsis: Sets the file name for this node
//
// Created: pmay
//
//-----------------------------------------------------------------------------
DWORD
PbkCacheNode::SetFileName(
    IN WCHAR* pszFileName)
{
    Free0(m_pszFileName);
    m_pszFileName = NULL;

    // Copy the file name
    //
    if (pszFileName)
    {
        m_pszFileName = StrDup( pszFileName );
        if (m_pszFileName == NULL)
        {
            return ERROR_NOT_ENOUGH_MEMORY;
        }
    }        

    return NO_ERROR;
}

//+----------------------------------------------------------------------------
//
// Synopsis: Instantiates the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
DWORD
PbkCacheNode::GetLastWriteTime(
    OUT FILETIME* pTime)
{
    BOOL fOk;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    DWORD dwErr = NO_ERROR;

//For whistler bug 537369       gangz
//We cannot leave the handle open for ever, have to close it after using it.
// so delete the m_hFile data member
// Rather, re-open it and get its information by handle

    if (m_pszFileName == NULL || TEXT('\0') == m_pszFileName[0] )
    {
        return ERROR_CAN_NOT_COMPLETE;
    }

    if( NULL == pTime )
    {
        return ERROR_INVALID_PARAMETER;
    }

    dwErr = GetFileLastWriteTime( m_pszFileName, pTime );

    return dwErr;
}

//+----------------------------------------------------------------------------
//
// Synopsis: Instantiates the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
DWORD
PbkCacheNode::Reload()
{
    DWORD dwErr = NO_ERROR;

    if (m_pszFileName == NULL)
    {
        return ERROR_CAN_NOT_COMPLETE;
    }

    do
    {
        Close();
        
        // Load the RasFile
        //
        dwErr = ReadPhonebookFileEx(
                    m_pszFileName, 
                    NULL, 
                    NULL, 
                    RPBF_NoCreate, 
                    &m_pbFile,
                    &m_ftRead);
        if (dwErr != NO_ERROR)
        {
            break;
        }
        
    } while (FALSE);

    // Cleanup
    //
    {
        if (dwErr != NO_ERROR)
        {   
            Close();
        }
    }

    return dwErr;
}

//+----------------------------------------------------------------------------
//
// Synopsis: Instantiates the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
PbkCache::PbkCache()
{
    ZeroMemory(m_Files, sizeof(m_Files));
}

//+----------------------------------------------------------------------------
//
// Synopsis: Destroys the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
PbkCache::~PbkCache()
{
    UINT i;
    
    DeleteCriticalSection(&m_csLock);
    
    for (i = 0; i < PBK_CACHE_MAX_RASFILES; i++)
    {
        if (m_Files[i])
        {
            delete m_Files[i];
            m_Files[i] = NULL;
        }
    }
}

//+----------------------------------------------------------------------------
//
// Synopsis: Initializes the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
DWORD
PbkCache::Initialize()
{
    DWORD dwErr = NO_ERROR;
    
    __try 
    {
        InitializeCriticalSection(&m_csLock);
    }
    __except (EXCEPTION_EXECUTE_HANDLER) 
    {
        dwErr = HRESULT_FROM_WIN32(GetExceptionCode());
    }

    return dwErr;
}

//+----------------------------------------------------------------------------
//
// Synopsis: Initializes the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
DWORD
PbkCache::GetEntry(
    IN WCHAR* pszPhonebook,
    IN WCHAR* pszEntry,
    OUT DTLNODE** ppEntryNode)
{
    DWORD dwErr, dwRet, dwSlot = PBK_CACHE_INVALID_SLOT;
    PbkCacheNode* pCacheNode = NULL;
    DTLNODE* pEntryNode, *pCopyNode = NULL;
    PBENTRY* pEntry;
    FILETIME ftWrite, ftRead;
    BOOL fFound = FALSE;

    TRACE2("PbkCache::GetEntry %S %S", pszPhonebook, pszEntry);

    Lock();
    
    do
    {
        FindFile(pszPhonebook, &pCacheNode, &dwSlot);
        
        // The file is not in the cache.  Insert it.
        //
        if (pCacheNode == NULL)
        {
            TRACE1("Inserting new pbk cache node %d", dwSlot);
            dwErr = InsertNewNode(pszPhonebook, dwSlot, &pCacheNode);
            if (dwErr != NO_ERROR)
            {
                break;
            }
        }

        // The file is in the cache.  Reload it if needed
        //
        else
        {
            ftRead = pCacheNode->GetReadTime();
            dwErr = pCacheNode->GetLastWriteTime(&ftWrite);
            if (dwErr != NO_ERROR)
            {
                break;
            }

            if (CompareFileTime(&ftRead, &ftWrite) < 0)
            {
                TRACE2("Reloading pbk cache %d (%S)", dwSlot, pszPhonebook);
                dwErr = pCacheNode->Reload();
                if (dwErr != NO_ERROR)
                {
                    break;
                }
            }
        }

        // Find the entry in question
        //
        dwErr = ERROR_CANNOT_FIND_PHONEBOOK_ENTRY;
        for (pEntryNode = DtlGetFirstNode( pCacheNode->GetEntryList() );
             pEntryNode;
             pEntryNode = DtlGetNextNode( pEntryNode ))
        {
            pEntry = (PBENTRY*) DtlGetData(pEntryNode);
            if (lstrcmpi(pEntry->pszEntryName, pszEntry) == 0)
            {
                fFound = TRUE;
                dwErr = NO_ERROR;
                pCopyNode = DuplicateEntryNode(pEntryNode);
                if (pCopyNode == NULL)
                {
                    dwErr = ERROR_NOT_ENOUGH_MEMORY;
                }
                break;
            }
        }
    
    } while (FALSE);

    Unlock();

    // Prepare the return value
    //
    if (pCopyNode)
    {
        *ppEntryNode = pCopyNode;
        dwRet = NO_ERROR;
    }
    else if (dwErr == ERROR_CANNOT_FIND_PHONEBOOK_ENTRY)
    {
        dwRet = dwErr;
    }
    else 
    {
        dwRet = ERROR_CANNOT_OPEN_PHONEBOOK;
    }

    TRACE3(
        "PbkCache::GetEntry returning 0x%x 0x%x fFound=%d", 
        dwErr, 
        dwRet, 
        fFound);

    return dwRet;
}

//+----------------------------------------------------------------------------
//
// Synopsis: Searches for a phonebook file in the cache.  If the file is not
//           in the cache, then the index in which to insert that file is 
//           returned.
//
//           Return values:
//              If file is found, *ppNode has node and *pdwIndex has the index
//              If file not found, *ppNode == NULL.  *pdwIndex has insert point
//              If cache is full and file not found:
//                      *ppNode == NULL, 
//                      *pdwIndex == PBK_CACHE_INVALID_SLOT
// Created: pmay
//
//-----------------------------------------------------------------------------
VOID
PbkCache::FindFile(
    IN WCHAR* pszFileName,
    OUT PbkCacheNode** ppNode,
    OUT DWORD* pdwIndex)
{
    DWORD i;

    *pdwIndex = PBK_CACHE_INVALID_SLOT;
    
    for (i = 0; i < PBK_CACHE_MAX_RASFILES; i++)
    {
        if (m_Files[i])
        {
            if (lstrcmpi(pszFileName, m_Files[i]->GetPath()) == 0)
            {
                *ppNode = m_Files[i];
                *pdwIndex = i;
                break;
            }
        }
        else
        {
            if (*pdwIndex == PBK_CACHE_INVALID_SLOT)
            {
                *pdwIndex = i;
            }                    
        }
    }
}

//+----------------------------------------------------------------------------
//
// Synopsis: Inserts a node in the give slot in the cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
DWORD
PbkCache::InsertNewNode(
    IN PWCHAR pszPhonebook,
    IN DWORD dwSlot,
    OUT PbkCacheNode** ppNode)
{
    PbkCacheNode* pCacheNode = NULL;
    DWORD dwErr = NO_ERROR;
    
    do
    {
        if (dwSlot == PBK_CACHE_INVALID_SLOT)
        {
            dwErr = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        pCacheNode = new PbkCacheNode();
        if (pCacheNode == NULL)
        {
            dwErr = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        dwErr = pCacheNode->SetFileName(pszPhonebook);
        if (dwErr != NO_ERROR)
        {
            break;
        }
        
        dwErr = pCacheNode->Reload();
        if (dwErr != NO_ERROR)
        {
            break;
        }

        m_Files[dwSlot] = pCacheNode; 
        *ppNode = pCacheNode;
        
    } while (FALSE);

    // Cleanup
    //
    {
        if (dwErr != NO_ERROR)
        {
            if (pCacheNode)
            {
                delete pCacheNode;
            }
        }
    }

    return dwErr;
}

//+----------------------------------------------------------------------------
//
// Synopsis: check if the phonebook cache is initialized
//
// Created: gangz
//
//-----------------------------------------------------------------------------
BOOL
IsPbkCacheInit()
{

    return g_pPbkCache ? TRUE : FALSE;
}

//+----------------------------------------------------------------------------
//
// Synopsis: Initializes the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
DWORD
PbkCacheInit()
{
    DWORD dwErr;

    ASSERT(g_pPbkCache == NULL);
    
    g_pPbkCache = new PbkCache;
    if (g_pPbkCache == NULL)
    {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    dwErr = g_pPbkCache->Initialize();
    if (dwErr != NO_ERROR)
    {
        delete g_pPbkCache;
        g_pPbkCache = NULL;
    }
    
    return dwErr;
}

//+----------------------------------------------------------------------------
//
// Synopsis: Cleans up the phonebook cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
VOID
PbkCacheCleanup()
{
    PbkCache* pCache = g_pPbkCache;

    g_pPbkCache = NULL;
    
    if (pCache)
    {
        delete pCache;
    }
}

//+----------------------------------------------------------------------------
//
// Synopsis: Gets an entry from the cache
//
// Created: pmay
//
//-----------------------------------------------------------------------------
DWORD
PbkCacheGetEntry(
    IN WCHAR* pszPhonebook,
    IN WCHAR* pszEntry,
    OUT DTLNODE** ppEntryNode)
{
    if (g_pPbkCache)
    {
        return g_pPbkCache->GetEntry(pszPhonebook, pszEntry, ppEntryNode);
    }

    return ERROR_CAN_NOT_COMPLETE;
}

#endif //endof #ifdef  _PBK_CACHE_