//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1997.
//
//  File:       T F I N D . C P P
//
//  Contents:   Asynchronous find mechanism for UPnP tray monitor.
//
//  Notes:
//
//  Author:     jeffspr   22 Nov 1999
//
//----------------------------------------------------------------------------

#include "pch.h"
#pragma hdrstop

#include <wininet.h>

#include "tfind.h"
#include "clist.h"
#include "clistndn.h"

#include "tconst.h"

extern UINT     g_iTotalBalloons;

CListString         g_CListUDN;
CListString         g_CListUDNSearch;
CListNDN            g_CListNewDeviceNode;
CListNameMap        g_CListNameMap;

BOOL                g_fIconPresent              = FALSE;

// (tongl)
// List of devices currently on the network
// This is the cached device list to show in "My Network Places" folder
CListFolderDeviceNode       g_CListFolderDeviceNode;
CRITICAL_SECTION            g_csFolderDeviceList;

// CLSID_NetworkNeighborhood
CONST WCHAR c_szNetworkNeighborhoodFolderPath[]   = L"::{208D2C60-3AEA-1069-A2D7-08002B30309D}\\";
// CLSID UPnP Delegate Folder - note "," is used for the new parse syntax (guru)
CONST WCHAR c_szUPnPFolderPath[] = L"::{e57ce731-33e8-4c51-8354-bb4de9d215d1},";
CONST WCHAR c_szDelegateFolderPrefix[]            = L"__upnpitem:";
CONST SIZE_T c_cchDelegateFolderPrefix            = celems(c_szDelegateFolderPrefix) - 1;

BOOL CListFolderDeviceNode::FCompare(FolderDeviceNode * pNode, PCWSTR pszUDN)
{
    // see whether UDNs match between two device nodes
    Assert(pNode);
    Assert(pszUDN);
    return (wcscmp(pNode->pszUDN, pszUDN) == 0);
}

BOOL CListNDN::FCompare(NewDeviceNode * pNode, LPCTSTR pszUDN)
{
    // see whether UDNs match between two device nodes
    Assert(pNode);
    Assert(pszUDN);
    return (_tcscmp(pNode->pszUDN, pszUDN) == 0);
}


BOOL CListString::FCompare(LPTSTR pszCurrentNodeString, LPCTSTR pszKey)
{
    // see whether UDNs match between two device nodes
    Assert(pszCurrentNodeString);
    Assert(pszKey);
    return (_tcscmp(pszCurrentNodeString, pszKey) == 0);
}

BOOL CListNameMap::FCompare(NAME_MAP *pnm, LPCTSTR szUdn)
{
    // see whether UDNs match between two device nodes
    Assert(pnm);
    Assert(szUdn);
    return (_tcscmp(pnm->szUdn, szUdn) == 0);
}

TCHAR * BSTRToTsz(BSTR bstrInput)
{
    return TszFromWsz(bstrInput);
}

// The string created is:
// "::CLSID_NetworkNeighborhood\\__upnpitem:UPNP_device_UDN"
//

// The new shell changes
// "::CLSID_NetworkNeighborhood\\::GUID for UPnP Delegate Folder,<parse string>
// <parse string >  __upnpitem:UDN of the device

LPWSTR
CreateChangeNotifyString(LPCWSTR pszUdn)
{
    LPWSTR pszNotifyString;

    pszNotifyString = new WCHAR [ MAX_PATH ];
    if (pszNotifyString)
    {
        Assert((celems(c_szNetworkNeighborhoodFolderPath) +
                celems(c_szDelegateFolderPrefix)) < MAX_PATH);

        CONST SIZE_T cchMax = MAX_PATH
                - (celems(c_szNetworkNeighborhoodFolderPath) * sizeof(WCHAR))
                - (celems(c_szUPnPFolderPath) * sizeof(WCHAR))
                - (celems(c_szDelegateFolderPrefix) * sizeof(WCHAR))
                + 2;  // +2 we have subtracted Three null characters

        // note: we know that the folder path and the prefix can fit
        //       in the MAX_PATH buffer
        wcscpy(pszNotifyString, c_szNetworkNeighborhoodFolderPath);
        wcscat(pszNotifyString, c_szUPnPFolderPath);
        wcscat(pszNotifyString, c_szDelegateFolderPrefix);
        wcsncat(pszNotifyString, pszUdn, cchMax);
    }
    else
    {
        TraceTag(ttidShellFolder, "CreateChangeNotifyString: new failed");
    }

    return pszNotifyString;
}


//+---------------------------------------------------------------------------
//
//  Function:   NewDeviceNode::NewDeviceNode
//
//  Purpose:    Initializes a NewDeviceNode structure.
//
//  Notes:
//

NewDeviceNode::NewDeviceNode()
{
    pszUDN = NULL;
    pszDisplayName = NULL;
    pszType = NULL;
    pszPresentationURL = NULL;
    pszManufacturerName = NULL;
    pszModelName = NULL;
    pszModelNumber = NULL;
    pszDescription = NULL;
}


//+---------------------------------------------------------------------------
//
//  Function:   NewDeviceNode::~NewDeviceNode
//
//  Purpose:    Deletes a NewDeviceNode structure.
//
//  Author:     donryan 18 Feb 2000
//
//  Notes:      Moved from CUPnPMonitorDeviceFinderCallback::DeviceAdded
//

NewDeviceNode::~NewDeviceNode()
{
    delete pszUDN;
    delete pszDisplayName;
    delete pszType;
    delete pszPresentationURL;
    delete pszManufacturerName;
    delete pszModelName;
    delete pszModelNumber;
    delete pszDescription;
}

//+---------------------------------------------------------------------------
//
//  Function:   HrMapUdnToFriendlyName
//
//  Purpose:    Given a UPnP device and UDN, maps the UDN to a friendly name
//              from the registry if it is present, otherwise falls back to
//              the friendly name from the device
//
//  Arguments:
//      pdev      [in]      UPnP device to check
//      bstrUdn   [in]      UDN of device
//      pbstrName [out]     Returns friendly name of device
//
//  Returns:    S_OK if success, E_OUTOFMEMORY if no memory, or Win32 error
//              code
//
//  Author:     danielwe   2000/10/25
//
//  Notes:
//
HRESULT HrMapUdnToFriendlyName(IUPnPDevice *pdev, BSTR bstrUdn, BSTR *pbstrName)
{
    HRESULT     hr = S_OK;
    NAME_MAP *  pnm = NULL;

    Assert(pbstrName);

    *pbstrName = NULL;

    if (g_CListNameMap.FFind(bstrUdn, &pnm))
    {
        Assert(pnm);

        *pbstrName = SysAllocString(pnm->szName);
        if (!*pbstrName)
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else
    {
        // UDN doesn't have a mapping in registry. Fall back to device's
        // friendly name
        //
        hr = pdev->get_FriendlyName(pbstrName);
    }

    TraceError("HrMapUdnToFriendlyName", hr);
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   HrCreateDeviceNodeFromDevice
//
//  Purpose:    Utility function to transfer information from device object
//              into NewDeviceNode structure.
//
//  Arguments:
//      pDevice [in]  The device pointer
//      ppNDN   [out] The pointer to the NewDeviceNode pointer.
//
//  Returns:
//
//  Author:     donryan 18 Feb 2000
//
//  Notes:      Moved from CUPnPMonitorDeviceFinderCallback::DeviceAdded
//
HRESULT
HrCreateDeviceNodeFromDevice(
    IUPnPDevice * pDevice,
    NewDeviceNode ** ppNDN
    )
{
    HRESULT hr                      = S_OK;
    BSTR    bstrUDN                 = NULL;
    BSTR    bstrDisplayName         = NULL;
    BSTR    bstrType                = NULL;
    BSTR    bstrPresentationURL     = NULL;
    BSTR    bstrManufacturerName    = NULL;
    BSTR    bstrModelName           = NULL;
    BSTR    bstrModelNumber         = NULL;
    BSTR    bstrDescription         = NULL;
    PTSTR   pszUDN                  = NULL;
    NewDeviceNode * pNDN            = NULL;

    Assert(pDevice);
    Assert(ppNDN);

    hr = pDevice->get_UniqueDeviceName(&bstrUDN);
    if (FAILED(hr))
    {
        TraceTag(ttidShellTray, "Error calling pDevice->get_UniqueDeviceName");
        goto Exit;
    }

    hr = HrMapUdnToFriendlyName(pDevice, bstrUDN, &bstrDisplayName);
    if (FAILED(hr))
    {
        goto Exit;
    }

    hr = pDevice->get_Type(&bstrType);
    if (FAILED(hr))
    {
        TraceTag(ttidShellTray, "Error calling pDevice->get_Type");
        goto Exit;
    }

    hr = pDevice->get_PresentationURL(&bstrPresentationURL);
    if (FAILED(hr))
    {
        TraceTag(ttidShellTray, "Error calling pDevice->get_PresentationURL");
        goto Exit;
    }

    hr = pDevice->get_ManufacturerName(&bstrManufacturerName);
    if (FAILED(hr))
    {
        TraceTag(ttidShellTray, "Error calling pDevice->get_ManufacturerName");
        goto Exit;
    }

    hr = pDevice->get_ModelName(&bstrModelName);
    if (FAILED(hr))
    {
        TraceTag(ttidShellTray, "Error calling pDevice->get_ModelName");
        goto Exit;
    }

    hr = pDevice->get_ModelNumber(&bstrModelNumber);
    if (FAILED(hr))
    {
        TraceTag(ttidShellTray, "Error calling pDevice->get_ModelNumber");
        goto Exit;
    }

    hr = pDevice->get_Description(&bstrDescription);
    if (FAILED(hr))
    {
        TraceTag(ttidShellTray, "Error calling pDevice->get_Description");
        goto Exit;
    }

    // Create a new Device node, copy the strings into it, and add it to the device map
    //
    pNDN = new NewDeviceNode;
    if (pNDN)
    {
        Assert(bstrUDN);
        pNDN->pszUDN = BSTRToTsz(bstrUDN);

        Assert(bstrDisplayName);
        pNDN->pszDisplayName = BSTRToTsz(bstrDisplayName);

        Assert(bstrType);
        pNDN->pszType = BSTRToTsz(bstrType);

        if (bstrPresentationURL)
        {
            pNDN->pszPresentationURL = BSTRToTsz(bstrPresentationURL);
        }
        else
        {
            pNDN->pszPresentationURL = new TCHAR [ 1 ];
            if (pNDN->pszPresentationURL)
            {
                pNDN->pszPresentationURL[0] = TEXT('\0');
            }
        }

        Assert(bstrManufacturerName);
        pNDN->pszManufacturerName = BSTRToTsz(bstrManufacturerName);

        Assert(bstrModelName);
        pNDN->pszModelName = BSTRToTsz(bstrModelName);

        if (bstrModelNumber)
        {
            pNDN->pszModelNumber = BSTRToTsz(bstrModelNumber);
        }
        else
        {
            pNDN->pszModelNumber = new TCHAR [ 1 ];
            if (pNDN->pszModelNumber)
            {
                pNDN->pszModelNumber[0] = TEXT('\0');
            }
        }

        if (bstrDescription)
        {
            pNDN->pszDescription = BSTRToTsz(bstrDescription);
        }
        else
        {
            pNDN->pszDescription = new TCHAR [ 1 ];
            if (pNDN->pszDescription)
            {
                pNDN->pszDescription[0] = TEXT('\0');
            }
        }

        // If they didn't all copy fine, delete them
        //
        if (!(pNDN->pszUDN && pNDN->pszDisplayName &&
            pNDN->pszType && pNDN->pszPresentationURL &&
            pNDN->pszManufacturerName && pNDN->pszModelName &&
            pNDN->pszModelNumber && pNDN->pszDescription))
        {
            hr = E_OUTOFMEMORY;
            delete pNDN;
            pNDN = NULL;
        }
    }
    else
    {
        TraceTag(ttidShellTray, "Could not allocate NewDeviceNode");
        hr = E_OUTOFMEMORY;
    }

Exit:

    // transfer
    *ppNDN = pNDN;

    SysFreeString(bstrUDN);
    SysFreeString(bstrDisplayName);
    SysFreeString(bstrPresentationURL);
    SysFreeString(bstrType);
    SysFreeString(bstrManufacturerName);
    SysFreeString(bstrModelName);
    SysFreeString(bstrModelNumber);
    SysFreeString(bstrDescription);

    TraceHr(ttidShellTray, FAL, hr, FALSE, "HrCreateDeviceNodeFromDevice");
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     HrAddFolderDevice
//
//  Purpose:
//
//  Arguments:
//      pDevice
//
//  Returns:
//
//  Author:     tongl   15 Feb 2000
//
//  Notes:
//
HRESULT HrAddFolderDevice(IUPnPDevice * pDevice)
{
    HRESULT hr = S_OK;

    BSTR    bstrUDN                 = NULL;
    BSTR    bstrDisplayName         = NULL;
    BSTR    bstrType                = NULL;
    BSTR    bstrPresentationURL     = NULL;
    BSTR    bstrDescription         = NULL;

    // (tongl) Per cmr, some device property (display name, presentation url)
    // could change on an existing device and this function will be called but
    // the device was not first removed. In this case we need to notify shell
    // to first remove the existing device then add the new one.
    BOOL    fUpdate = FALSE;
    BOOL    fUpdateOldDevice = FALSE;

    // fNewNode - If its really a new device
    // fUpdate  - The device is in MNP but must be updated bcoz device property has 
    //            changed. Note above.
    // fUpdateOldDevice - An old device has sent bye bye and later comes up alive
    //                   with same UDN. We have cached the list of UDNs. 
    //                   

    Assert(pDevice);

    pDevice->AddRef();
    hr = pDevice->get_UniqueDeviceName(&bstrUDN);
    if (SUCCEEDED(hr))
    {
        // If we're doing a search right now, just add this UDN to the list
        // of devices that we've found so far in the search
        //
        if (g_fSearchInProgress)
        {
            if (!g_CListUDNSearch.FAdd(WszDupWsz(bstrUDN)))
            {
                hr = E_OUTOFMEMORY;
            }
        }
    }
    else
    {
        TraceTag(ttidShellFolder, "Failed in pDevice->get_UniqueDeviceName from HrAddFolderDevice");
    }

    if (SUCCEEDED(hr))
    {
        hr = HrMapUdnToFriendlyName(pDevice, bstrUDN, &bstrDisplayName);
        if (SUCCEEDED(hr))
        {
            hr = pDevice->get_Type(&bstrType);
            if (SUCCEEDED(hr))
            {
                hr = pDevice->get_PresentationURL(&bstrPresentationURL);
                if (SUCCEEDED(hr))
                {
                    hr = pDevice->get_Description(&bstrDescription);
                    if (SUCCEEDED(hr))
                    {
                        BOOL    fNewNode;

                        EnterCriticalSection(&g_csFolderDeviceList);

                        FolderDeviceNode * pNewDevice = NULL;
                        if( g_CListFolderDeviceNode.FFind((PWSTR)bstrUDN, &pNewDevice))
                        {
                            Assert(pNewDevice);

                            fNewNode = FALSE;

                            if (!pNewDevice->fDeleted)
                            {
                                // Only update if friendly name or description
                                // changed.
                                //
                                if ((wcscmp(pNewDevice->pszDescription, bstrDescription)) ||
                                    (wcscmp(pNewDevice->pszDisplayName, bstrDisplayName)))
                                {
                                    fUpdate = TRUE;
                                }
                            }
                            else
                            {
                                fUpdateOldDevice = TRUE;
                            }
                                
                        }
                        else
                        {
                            // truely a new device
                            pNewDevice = new FolderDeviceNode;

                            fNewNode = TRUE;

                            if (!pNewDevice)
                            {
                                hr = E_OUTOFMEMORY;
                            }
                        }

                        if (pNewDevice)
                        {
                            CONST SIZE_T cchMax = MAX_PATH - 1;

                            pNewDevice->fDeleted = FALSE;

                            Assert(bstrUDN);
                            wcscpy(pNewDevice->pszUDN, L"");
                            wcsncat(pNewDevice->pszUDN,(PWSTR)bstrUDN, cchMax);

                            Assert(bstrDisplayName);
                            wcscpy(pNewDevice->pszDisplayName, L"");
                            wcsncat(pNewDevice->pszDisplayName, bstrDisplayName, cchMax);

                            Assert(bstrType);
                            wcscpy(pNewDevice->pszType, L"");
                            wcsncat(pNewDevice->pszType, (PWSTR)bstrType, cchMax);

                            wcscpy(pNewDevice->pszPresentationURL, L"");
                            if (bstrPresentationURL)
                            {
                                wcsncat(pNewDevice->pszPresentationURL,
                                        (PWSTR)bstrPresentationURL,
                                        cchMax);
                            }

                            wcscpy(pNewDevice->pszDescription, L"");
                            if (bstrDescription)
                            {
                                wcsncat(pNewDevice->pszDescription,
                                        (PWSTR)bstrDescription,
                                        cchMax);
                            }

                            // add to our list if a true new device
                            if (fNewNode)
                            {
                                g_CListFolderDeviceNode.FAdd(pNewDevice);

                                TraceTag(ttidShellFolder, "HrAddFolderDevice: Added %S to g_CListFolderDeviceNode", bstrDisplayName);
                            }
                            else
                            {
                                TraceTag(ttidShellFolder, "HrAddFolderDevice: Modified %S in g_CListFolderDeviceNode", bstrDisplayName);
                            }
                            TraceTag(ttidShellFolder, "HrAddFolderDevice: Now, g_CListFolderDeviceNode has %d elements", g_CListFolderDeviceNode.GetCount());

                        }
                        LeaveCriticalSection(&g_csFolderDeviceList);

                        if (fNewNode || fUpdate || fUpdateOldDevice)
                        {
                            if (SUCCEEDED(hr))
                            {
                                // notify shell to add the new device
                                LPWSTR pszNotifyString;

                                pszNotifyString = CreateChangeNotifyString(bstrUDN);
                                if (pszNotifyString)
                                {
                                    if (fUpdate)
                                    {
                                        TraceTag(ttidShellFolder, "Generating update device event for %S", bstrDisplayName);
                                        SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, pszNotifyString, NULL);
                                    }
                                    else
                                    {
                                        // Assert(fNewNode);
                                        // fNewNode or fUpdateOldDevice 
                                        TraceTag(ttidShellFolder, "Generating new device event for %S", bstrDisplayName);
                                        SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, pszNotifyString, NULL);
                                    }

                                    delete [] pszNotifyString;
                                }
                            }
                            else
                            {
                                // Don't make a failure here.
                                //
                                TraceTag(ttidShellFolder, "Couldn't add device to list in CUPnPDeviceFolderDeviceFinderCallback::DeviceAdded");
                            }
                        }
                        else
                        {
                            TraceTag(ttidShellFolder, "Nothing about device %S:%S changed", bstrUDN, bstrDisplayName);
                        }
                    }
                    else
                    {
                        TraceTag(ttidShellFolder, "Failed in get_Description from HrAddFolderDevice");
                    }
                }
                else
                {
                    TraceTag(ttidShellFolder, "Failed in get_PresentationURL from HrAddFolderDevice");
                }
            }
            else
            {
                TraceTag(ttidShellFolder, "Failed in pDevice->get_Type from HrAddFolderDevice");
            }
        }
        else
        {
            TraceTag(ttidShellFolder, "Failed in pDevice->get_FriendlyName from HrAddFolderDevice");
        }
    }

    SysFreeString(bstrUDN);
    SysFreeString(bstrDisplayName);
    SysFreeString(bstrPresentationURL);
    SysFreeString(bstrType);
    SysFreeString(bstrDescription);

    pDevice->Release();

    TraceHr(ttidError, FAL, hr, (S_FALSE == hr), "HrAddFolderDevice");
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     HrDeleteFolderDevice
//
//  Purpose:
//
//  Arguments:
//      szUDN
//
//  Returns:
//
//  Author:     tongl   18 Feb 2000
//
//  Notes:
//

HRESULT HrDeleteFolderDevice(PWSTR  szUDN)
{
    HRESULT hr = S_OK;
    FolderDeviceNode * pDevice = NULL;

    EnterCriticalSection(&g_csFolderDeviceList);
    if (g_CListFolderDeviceNode.FFind(szUDN, &pDevice))
    {
        pDevice->fDeleted = TRUE;
    }
    else
    {
        // can't delete a device that's not in our cache
        TraceTag(ttidShellFolder, "The device to delete is not in the cache: %S.", szUDN);
        hr = E_FAIL;
    }
    LeaveCriticalSection(&g_csFolderDeviceList);

    if (SUCCEEDED(hr))
    {
        // notify shell to delete the device
        LPWSTR pszNotifyString;

        pszNotifyString = CreateChangeNotifyString(szUDN);
        if (pszNotifyString)
        {
            TraceTag(ttidShellFolder, "Delete device event for %S", szUDN);
            SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, pszNotifyString, NULL);

            delete [] pszNotifyString;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }

    TraceError("HrDeleteFolderDevice", hr);
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CUPnPMonitorDeviceFinderCallback::DeviceAdded
//
//  Purpose:    Our callback for "new device" -- duh. Here we get the
//              important properties, pack them in a struct, and store them
//              in our device list. We'll use this data if the user opens
//              properties on one of the dialogs, or adds shortcuts.
//
//  Arguments:
//      lFindData  [in] Our callback ID
//      pDevice    [in] The device pointer.
//
//  Returns:
//
//  Author:     jeffspr   23 Nov 1999
//
//  Notes:
//
HRESULT CUPnPMonitorDeviceFinderCallback::DeviceAdded(LONG lFindData,
    IUPnPDevice *   pDevice)
{
    HRESULT hr           = S_OK;
    NewDeviceNode * pNDN = NULL;

    Assert(pDevice);
    pDevice->AddRef();

#if DBG

    BSTR bstrUDN = NULL;
    hr = pDevice->get_UniqueDeviceName(&bstrUDN);

    TraceTag(ttidShellTray, "DeviceFinderCallback -- New Device. SearchId: %x, UDN: %S",
             lFindData, bstrUDN);

    SysFreeString(bstrUDN);

#endif // DBG

    BSTR    bstrPresUrl;

    hr = pDevice->get_PresentationURL(&bstrPresUrl);
    if (S_OK == hr)
    {
        URL_COMPONENTS  urlComp = {0};

        TraceTag(ttidShellTray, "Checking if %S is a valid URL...",
                 bstrPresUrl);

        // All we want to do here is verify that the URL is valid. We don't
        // need anything back from this function
        //
        urlComp.dwStructSize = sizeof(URL_COMPONENTS);
        if (!InternetCrackUrl(bstrPresUrl, 0, 0, &urlComp))
        {
            TraceTag(ttidShellTray, "%S is NOT a valid URL!", bstrPresUrl);
            hr = HrFromLastWin32Error();
        }

        SysFreeString(bstrPresUrl);
    }
    else
    {
        hr = E_FAIL;
        TraceError("Device did not have presentation URL!", hr);
    }

    if (SUCCEEDED(hr))
    {
        // (tongl)
        // add the device to our folder device list and notify shell in case folder is open
        hr = HrAddFolderDevice(pDevice);
        if (SUCCEEDED(hr))
        {
            // transfer information from device object
            hr = HrCreateDeviceNodeFromDevice(pDevice, &pNDN);
            if (SUCCEEDED(hr))
            {
                // Assuming we don't already have this device is our list, add it.
                //
                if (!g_CListUDN.FFind(pNDN->pszUDN, NULL))
                {
                    // Add it to the New Device list.
                    //
                    g_CListNewDeviceNode.FAdd(pNDN);

                    // reset the total balloon count
                    g_iTotalBalloons =0;
                }
                else
                {
                    // Cleanup already known device
                    //
                    delete pNDN;
                }
            }
        }
    }

    pDevice->Release();

    if (SUCCEEDED(hr))
    {
        if (!g_fSearchInProgress)
        {
            hr = HrUpdateTrayInfo();
        }
    }

    TraceError("CUPnPMonitorDeviceFinderCallback::DeviceAdded", hr);
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Member:     CUPnPMonitorDeviceFinderCallback::DeviceRemoved
//
//  Purpose:    Device removed notification. NYI
//
//  Arguments:
//      lFindData []
//      bstrUDN   []
//
//  Returns:
//
//  Author:     jeffspr   23 Nov 1999
//
//  Notes:
//
HRESULT CUPnPMonitorDeviceFinderCallback::DeviceRemoved(
    LONG    lFindData,
    BSTR    bstrUDN)
{
    TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback::DeviceRemoved"
             " lFindData = %x, UDN = %S", lFindData, bstrUDN);

    Assert(bstrUDN);

    // (tongl)
    // delete the device from our folder device list and notify shell in case folder is open
    HrDeleteFolderDevice((PWSTR)bstrUDN);

    HRESULT hr = S_OK;
    LPTSTR pszUdn;
    BOOL fResult;

    pszUdn = BSTRToTsz(bstrUDN);
    if (!pszUdn)
    {
        TraceTag(ttidShellTray, "Could not copy UDN to TCHAR");
        hr = E_OUTOFMEMORY;
        goto Exit;
    }

    // search through new device list for new node, removing any nodes
    // with matching UDNs
    //
    fResult = g_CListNewDeviceNode.FDelete(pszUdn);
    if (!fResult)
    {
        // node wasn't deleted

        TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback::DeviceRemoved: "
                 "%S not found in g_CListNewDeviceNode", pszUdn);
    }

    // update tray information if search has completed
    if (!g_fSearchInProgress)
    {
        hr = HrUpdateTrayInfo();
    }
    else
    {
        // search is still running so delete this from the search list
        fResult = g_CListUDNSearch.FDelete(pszUdn);
        if (!fResult)
        {
            // node wasn't deleted

            TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback::DeviceRemoved: "
                     "%S not found in g_CListUDNSearch", pszUdn);
        }
    }

Exit:
    delete [] pszUdn;

    TraceError("CUPnPMonitorDeviceFinderCallback::DeviceRemoved", hr);
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Member:     CUPnPMonitorDeviceFinderCallback::SearchComplete
//
//  Purpose:    Search complete. At this point I can add the tray icon
//              if needed and allow for the UI to come up.
//
//  Arguments:
//      lFindData []
//
//  Returns:
//
//  Author:     jeffspr   6 Jan 2000
//
//  Notes:
//
HRESULT CUPnPMonitorDeviceFinderCallback::SearchComplete(LONG lFindData)
{
    TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback::SearchComplete"
             " lFindData = %x", lFindData);

    HRESULT hr  = S_OK;

    g_fSearchInProgress = FALSE;

    // Add the tray icon and such (if appropriate)
    //
    hr = HrInitializeUI();
    if (SUCCEEDED(hr))
    {
        EnterCriticalSection(&g_csFolderDeviceList);

        FolderDeviceNode * pCurrentNode = NULL;
        BOOL fReturn = g_CListFolderDeviceNode.FFirst(&pCurrentNode);

        // Loop through all UDNs in the cached list of devices we've found
        // and for each one, see if it was found during the search. If not,
        // we should delete it from the folder view and from the cache.
        while (fReturn && SUCCEEDED(hr))
        {
            LPWSTR  szUdn;

            if (!g_CListUDNSearch.FFind(pCurrentNode->pszUDN, &szUdn))
            {
                hr = HrDeleteFolderDevice(pCurrentNode->pszUDN);
                if (SUCCEEDED(hr))
                {
                    if (!g_CListFolderDeviceNode.FDelete(pCurrentNode->pszUDN))
                    {
                        hr = E_FAIL;
                    }
                }
            }

            // move to the next node
            fReturn = g_CListFolderDeviceNode.FNext(&pCurrentNode);
        }

        LeaveCriticalSection(&g_csFolderDeviceList);
    }

    g_CListUDNSearch.Flush();

    TraceError("CUPnPMonitorDeviceFinderCallback::SearchComplete", hr);
    return S_OK;
}

CUPnPMonitorDeviceFinderCallback::CUPnPMonitorDeviceFinderCallback()
{
    m_pUnkMarshaler = NULL;
    TraceTag(ttidShellTray, "CUPnPMonitorDeviceFinderCallback");
}

CUPnPMonitorDeviceFinderCallback::~CUPnPMonitorDeviceFinderCallback()
{
    TraceTag(ttidShellTray, "~CUPnPMonitorDeviceFinderCallback");
}