//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1997 - 1999
//
//  File:       sharecnx.cpp
//
//--------------------------------------------------------------------------

#include "pch.h"
#pragma hdrstop

#include "sharecnx.h"

//
// This class is a simple cache of net share names and some status flags.
// Initially, the only status maintained is to remember if there's an
// an open net connection for the share.
// The motivation for the cache is to avoid expensive net calls while
// we're cruising through lists of files (i.e. deleting files from the
// cache).  After we delete a file from the cache, it is effectively
// unpinned.  Therefore, we like to notify the shell so it can remove
// the "pinned" icon overlay from the affected file(s).  However,
// we don't want to hit the net with a change notify if there
// isn't an open connection to a file's parent share.  Before we issue
// a change notify, we just query this cache for a file using
// IsOpenConnectionPathUNC().  If there's no entry for the file's share,
// one is added and the connection status is obtained.  If there's
// already an entry, then we just return the status.  The public APIs
// support a "refresh" flag if refresh is desired.
// Additional status information could easily be added for each entry
// if it's needed later.
// [brianau - 12/12/98]
//

//-----------------------------------------------------------------------------
// CShareCnxStatusCache member functions.
//-----------------------------------------------------------------------------

CShareCnxStatusCache::CShareCnxStatusCache(
    void
    ) : m_hdpa(NULL)
{

}



CShareCnxStatusCache::~CShareCnxStatusCache(
    void
    )
{
    if (NULL != m_hdpa)
    {
        //
        // Delete all the entries then destroy the DPA.
        //
        int cEntries = Count();
        for (int i = 0; i < cEntries; i++)
        {
            delete GetEntry(i);
        }
        DPA_Destroy(m_hdpa);
    }
}


//
// Returns address of entry or NULL if not found.
//
CShareCnxStatusCache::Entry *
CShareCnxStatusCache::FindEntry(
    LPCTSTR pszShare
    ) const
{
    if (NULL != m_hdpa)
    {
        int cEntries = Count();
        for (int i = 0; i < cEntries; i++)
        {
            Entry *pEntry = GetEntry(i);
            if (NULL != pEntry && NULL != pEntry->Share())
            {
                if (0 == lstrcmpi(pszShare, pEntry->Share()))
                {
                    //
                    // Aha, we found a match.
                    //
                    return pEntry;
                }
            }
        }
    }
    return NULL;
}

    
//
// Creates a new entry and adds it to the DPA of entries.
// If successful, returns address of new entry.
// Does not check for duplicate entry before adding new one.
//
CShareCnxStatusCache::Entry *
CShareCnxStatusCache::AddEntry(
    LPCTSTR pszShare,
    DWORD dwStatus
    )
{
    Entry *pEntry = NULL;

    if (NULL == m_hdpa)
    {
        //
        // Must be first addition.  Create the DPA.
        //
        m_hdpa = DPA_Create(8);
    }

    if (NULL != m_hdpa)
    {
        int iEntry = -1;
        pEntry = new Entry(pszShare, dwStatus);
        if (NULL != pEntry && pEntry->IsValid())
        {
            //
            // We have a valid entry. Add it to the DPA.
            //
            iEntry = DPA_AppendPtr(m_hdpa, pEntry);
        }
        if (-1 == iEntry)
        {
            //
            // One of the following bad things happened:
            //
            //   1. Entry creation failed.  Most likely couldn't alloc string.
            //   2. Failed to add entry to DPA (out of memory).
            // 
            // Either way, destroy the entry and set the entry ptr so we 
            // return NULL.
            //
            delete pEntry;
            pEntry = NULL;
        }
    }
    return pEntry;
}        



//
// Determine if the net share associated with a UNC path (file or folder)
// has an open connection on this machine.
//
// Returns:
//    S_OK          = Open connection.
//    S_FALSE       = No open connection.
//    E_OUTOFMEMORY
//
HRESULT
CShareCnxStatusCache::IsOpenConnectionPathUNC(
    LPCTSTR pszPathUNC,
    bool bRefresh       // [optional].  Default = false.
    )
{
    //
    // Trim the path back to just the UNC share name.
    // Call IsOpenConnectionShare() to do the actual work.
    //
    TCHAR szShare[MAX_PATH * 2];
    lstrcpyn(szShare, pszPathUNC, ARRAYSIZE(szShare));
    PathStripToRoot(szShare);
    return IsOpenConnectionShare(szShare);
}



//
// Determine if the net share has an open connection on this machine.
//
// Returns:
//    S_OK     = Open connection.
//    S_FALSE  = No open connection.
//
HRESULT
CShareCnxStatusCache::IsOpenConnectionShare(
    LPCTSTR pszShare, 
    bool bRefresh       // [optional].  Default = false.
    )
{
    DWORD dwStatus    = 0;
    HRESULT hr = GetShareStatus(pszShare, &dwStatus, bRefresh);
    if (SUCCEEDED(hr))
    {
        if (0 != (dwStatus & Entry::StatusOpenCnx))
            hr = S_OK;
        else
            hr = S_FALSE;
    }
    return hr;
}



//
// Returns:
//
//      E_INVALIDARG = Path was not a UNC share.
//      S_OK         = Status is valid.
//
HRESULT
CShareCnxStatusCache::GetShareStatus(
    LPCTSTR pszShare, 
    DWORD *pdwStatus,
    bool bRefresh       // [optional].  Default = false.
    )
{
    HRESULT hr = E_INVALIDARG;  // Assume share name isn't UNC.
    *pdwStatus = 0;

    if (PathIsUNCServerShare(pszShare))
    {
        //
        // We have a valid UNC "\\server\share" name string.
        //
        Entry *pEntry = FindEntry(pszShare);
        if (NULL == pEntry)
        {
            //
            // Cache miss.  Get the system status for the share and try to 
            // cache it.
            //
            hr = Entry::QueryShareStatus(pszShare, pdwStatus);
            if (SUCCEEDED(hr))
            {
                //
                // Note that we don't return any errors from the cache attempt.
                // The only problem of not caching the entry is that the next 
                // call to this function will need to re-query the system for
                // the information.  This makes the cache meaningless but it's
                // not worth failing the information request.  Just slows things
                // down a bit.
                //
                AddEntry(pszShare, *pdwStatus);
            }
        }
        else 
        {
            //
            // Cache hit.
            //
            hr = S_OK;
            if (bRefresh)
            {
                //
                // Caller want's fresh info.
                //
                hr = pEntry->Refresh();
            }
            *pdwStatus = pEntry->Status();
            if (SUCCEEDED(hr))
                hr = pEntry->LastResult();
        }
    }
    return hr;
}



//
// Returns number of entries in the cache.
//
int 
CShareCnxStatusCache::Count(
    void
    ) const
{
    return (NULL != m_hdpa) ? DPA_GetPtrCount(m_hdpa) : 0;
}


//-----------------------------------------------------------------------------
// CShareCnxStatusCache::Entry member functions.
//-----------------------------------------------------------------------------

CShareCnxStatusCache::Entry::Entry(
    LPCTSTR pszShare,
    DWORD dwStatus
    ) : m_pszShare(StrDup(pszShare)),
        m_dwStatus(dwStatus),
        m_hrLastResult(NOERROR)
{
    if (NULL == m_pszShare)
    {
        m_hrLastResult = E_OUTOFMEMORY;
    }
}



CShareCnxStatusCache::Entry::~Entry(
    void
    )
{
    if (NULL != m_pszShare)
    {
        LocalFree(m_pszShare);
    }
}



//
// Obtain new status info for the entry.
// Returns true if no errors obtaining info, false if there were errors.
//
HRESULT
CShareCnxStatusCache::Entry::Refresh(
    void
    )
{
    m_dwStatus     = 0;
    m_hrLastResult = E_OUTOFMEMORY;

    if (NULL != m_pszShare)
        m_hrLastResult = QueryShareStatus(m_pszShare, &m_dwStatus);

    return m_hrLastResult;
}



//
// Static function for obtaining entry status info from the
// system.  Made this a static function so the cache object
// can obtain information before creating the entry.  In case
// entry creation fails, we still want to be able to return
// valid status info.
//
HRESULT
CShareCnxStatusCache::Entry::QueryShareStatus(
    LPCTSTR pszShare,
    DWORD *pdwStatus
    )
{
    HRESULT hr = NOERROR;
    *pdwStatus = 0;

    //
    // Check the open connection status for this share.
    //
    hr = ::IsOpenConnectionShare(pszShare);
    switch(hr)
    {
        case S_OK:
            //
            // Open connection found.
            //
            *pdwStatus |= StatusOpenCnx;
            break;

        case S_FALSE:
            hr = S_OK;
            break;

        default:
            break;
    }

    //
    // If any other status information is required in the future,
    // here's where you collect it from the system.
    //
    return hr;
}

