//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1995 - 1995.
//
//  File:       sfolder.cxx
//
//  Contents:   Implementation of IShellFolder
//
//  History:    13-Dec-95    BruceFo     Created
//
//----------------------------------------------------------------------------

#include "headers.hxx"
#pragma hdrstop

#include "dutil.hxx"
#include "enum.hxx"
#include "menuutil.hxx"
#include "menu.hxx"
#include "menusp.hxx"
#include "menubg.hxx"
#include "sdetails.hxx"
#include "sfolder.hxx"
#include "shares.h"
#include "shares.hxx"
#include "util.hxx"
#include "xicon.hxx"
#include "resource.h"

//////////////////////////////////////////////////////////////////////////////

void
FSSetStatusText(
    HWND hwndOwner,
    LPTSTR* ppszText,
    int iStart,
    int iEnd);

//////////////////////////////////////////////////////////////////////////////

STDMETHODIMP
CSharesSF::ParseDisplayName(
    HWND hwndOwner,
    LPBC pbc,
    LPOLESTR lpszDisplayName,
    ULONG* pchEaten,
    LPITEMIDLIST* ppidlOutm,
    ULONG* pdwAttributes
    )
{
    return E_NOTIMPL;
}


STDMETHODIMP
CSharesSF::GetAttributesOf(
    UINT cidl,
    LPCITEMIDLIST* apidl,
    ULONG* pdwInOut
    )
{
    // There are four types of object: New object, View NetWare, View Mac,
    // and regular share. If there is a single selection, then the operations
    // possible are:
    //      New share:      open, create shortcut
    //      View NetWare:   open, create shortcut
    //      View Mac:       open, create shortcut
    //      a share:        delete, properties
    // If there are different types of objects multiply selected, then
    // the items must all be shares, or there are no allowed operations.
    // For shares, the only multiple-select operation allowed is delete.

    ULONG fMask = 0;

    if (cidl == 0)
    {
        // What things in general can be done in the folder? Return a
        // mask of everything possible.
        fMask = SFGAO_CANDELETE | SFGAO_HASPROPSHEET | SFGAO_CANRENAME | SFGAO_LINK;
    }
    else if (cidl == 1)
    {
        LPIDSHARE pids = (LPIDSHARE)apidl[0];
        if (Share_IsShare(pids))
        {
            fMask = SFGAO_CANDELETE | SFGAO_HASPROPSHEET;
            if (!(Share_GetType(pids) & STYPE_SPECIAL))
            {
                fMask |= SFGAO_CANRENAME;
            }
        }
        else
        {
            fMask = SFGAO_CANLINK;
        }
    }
    else if (cidl > 1)
    {
        UINT i;
        for (i = 0; i < cidl; i++)
        {
            LPIDSHARE pids = (LPIDSHARE)apidl[i];
            if (!Share_IsShare(pids))
            {
                break;
            }
        }

        if (i == cidl)
        {
            fMask |= SFGAO_CANDELETE;
        }
    }

    *pdwInOut &= fMask;
    return S_OK;
}


STDMETHODIMP
CSharesSF::GetUIObjectOf(
    HWND hwndOwner,
    UINT cidl,
    LPCITEMIDLIST* apidl,
    REFIID riid,
    UINT* prgfInOut,
    LPVOID* ppvOut
    )
{
    CShares* This = IMPL(CShares,m_ShellFolder,this);
    HRESULT hr = E_NOINTERFACE;

    *ppvOut = NULL;

    if (cidl == 1 && IsEqualIID(riid, IID_IExtractIcon))
    {
        LPIDSHARE pids = (LPIDSHARE)apidl[0];

        CSharesEI* pObj = new CSharesEI(Share_GetFlags(pids), Share_GetType(pids));
        if (NULL == pObj)
        {
            return E_OUTOFMEMORY;
        }

        hr = pObj->QueryInterface(riid, ppvOut);
        pObj->Release();
    }
#ifdef UNICODE
    else if (cidl == 1 && IsEqualIID(riid, IID_IExtractIconA))
    {
        LPIDSHARE pids = (LPIDSHARE)apidl[0];

        CSharesEIA* pObj = new CSharesEIA(Share_GetFlags(pids), Share_GetType(pids));
        if (NULL == pObj)
        {
            return E_OUTOFMEMORY;
        }

        hr = pObj->QueryInterface(riid, ppvOut);
        pObj->Release();
    }
#endif // UNICODE
    else if (cidl > 0 && IsEqualIID(riid, IID_IContextMenu))
    {
        // Create a context menu for selected items. If there is only one
        // item, then the context menu is based on that object and is
        // CSharesCM for shares and CSharesCMSpecial for special objects.
        // If there is a multiple selection, then the selection must all be
        // shares, in which case the context-menu is CSharesCM, else there
        // is no context menu!

        if (This->m_level < 2)
        {
            // user has insufficient privilege to perform any operations.
            return E_NOINTERFACE;
        }

        IUnknown* punk = NULL;
        if (cidl == 1)
        {
            LPIDSHARE pids = (LPIDSHARE)apidl[0];
            if (Share_IsShare(pids))
            {
                CSharesCM* pObj = new CSharesCM(hwndOwner);
                if (NULL == pObj)
                {
                    return E_OUTOFMEMORY;
                }

                hr = pObj->InitInstance(This->m_pszMachine, cidl, apidl, this);
                if (FAILED(hr))
                {
                    return hr;
                }

                punk = (IUnknown*)pObj;
            }
#ifdef WIZARDS
            else
            {
                CSharesCMSpecial* pObj = new CSharesCMSpecial(hwndOwner);
                if (NULL == pObj)
                {
                    return E_OUTOFMEMORY;
                }

                hr = pObj->InitInstance(This->m_pszMachine, apidl[0], this);
                if (FAILED(hr))
                {
                    return hr;
                }

                punk = (IUnknown*)pObj;
            }
#endif // WIZARDS
        }
        else if (cidl > 1)
        {
            UINT i;
            for (i = 0; i < cidl; i++)
            {
                LPIDSHARE pids = (LPIDSHARE)apidl[i];
                if (!Share_IsShare(pids))
                {
                    break;
                }
            }

            if (i == cidl)
            {
                CSharesCM* pObj = new CSharesCM(hwndOwner);
                if (NULL == pObj)
                {
                    return E_OUTOFMEMORY;
                }

                hr = pObj->InitInstance(This->m_pszMachine, cidl, apidl, this);
                if (FAILED(hr))
                {
                    return hr;
                }

                punk = (IUnknown*)pObj;
            }
            else
            {
                return E_FAIL;
            }
        }

        appAssert(NULL != punk);
        hr = punk->QueryInterface(riid, ppvOut);
        punk->Release();
    }
    else if (cidl > 0 && IsEqualIID(riid, IID_IDataObject))
    {
        hr = CIDLData_CreateFromIDArray(
                        This->m_pidl,
                        cidl,
                        apidl,
                        (LPDATAOBJECT *)ppvOut);
    }

    return hr;
}


STDMETHODIMP
CSharesSF::EnumObjects(
    HWND hwndOwner,
    DWORD grfFlags,
    LPENUMIDLIST* ppenumUnknown
    )
{
    CShares* This = IMPL(CShares,m_ShellFolder,this);
    HRESULT hr = E_FAIL;

    *ppenumUnknown = NULL;

    if (!(grfFlags & SHCONTF_NONFOLDERS))
    {
        return hr;
    }

    appAssert(0 != This->m_level);
    CSharesEnum* pEnum = new CSharesEnum(This->m_pszMachine, This->m_level);
    if (NULL == pEnum)
    {
        return E_OUTOFMEMORY;
    }

    hr = pEnum->Init(grfFlags);
    if (FAILED(hr))
    {
        return hr;
    }

    hr = pEnum->QueryInterface(IID_IEnumIDList, (LPVOID*)ppenumUnknown);
    pEnum->Release();
    return hr;
}


STDMETHODIMP
CSharesSF::BindToObject(
    LPCITEMIDLIST pidl,
    LPBC pbc,
    REFIID riid,
    LPVOID* ppvOut
    )
{
    //
    // Shares folder doesn't contain sub-folders
    //

    *ppvOut = NULL;
    return E_FAIL;
}


// not used in Win95
STDMETHODIMP
CSharesSF::BindToStorage(
    LPCITEMIDLIST pidl,
    LPBC pbcReserved,
    REFIID riid,
    LPVOID* ppvOut
    )
{
    *ppvOut = NULL;
    return E_NOTIMPL;
}

#define PlusMinus(x) (((x) < 0) ? -1 : ( ((x) > 0) ? 1 : 0 ))

int
CSharesSF::_CompareOne(
    DWORD iCol,
    LPIDSHARE pids1,
    LPIDSHARE pids2
    )
{
    switch (iCol)
    {
    case ICOL2_NAME:
        return lstrcmpi(Share_GetName(pids1), Share_GetName(pids2));

    case ICOL2_COMMENT:
        return lstrcmpi(Share_GetComment(pids1), Share_GetComment(pids2));

    case ICOL2_PATH:
        return lstrcmpi(Share_GetPath(pids1), Share_GetPath(pids2));

    case ICOL2_MAXUSES:
    {
        DWORD max1 = Share_GetMaxUses(pids1);
        DWORD max2 = Share_GetMaxUses(pids2);
        if (max1 == SHI_USES_UNLIMITED && max2 == SHI_USES_UNLIMITED)
        {
            return 0;
        }
        else if (max1 == SHI_USES_UNLIMITED)
        {
            return 1;
        }
        else if (max2 == SHI_USES_UNLIMITED)
        {
            return -1;
        }
        else
        {
            return max1 - max2;
        }
    }

    default: appAssert(!"Illegal column"); return 0;
    }
}

STDMETHODIMP
CSharesSF::CompareIDs(
    LPARAM iCol,
    LPCITEMIDLIST pidl1,
    LPCITEMIDLIST pidl2
    )
{
    CShares* This = IMPL(CShares,m_ShellFolder,this);

    // If one item is a special item, then put it ahead of the other one.
    // If they are both special items, sort on name.

    LPIDSHARE pids1 = (LPIDSHARE)pidl1;
    LPIDSHARE pids2 = (LPIDSHARE)pidl2;
    int iCmp;

#ifdef WIZARDS
    if (Share_IsSpecial(pids1))
    {
        if (Share_IsSpecial(pids2))
        {
            // both special; sort on name
            return ResultFromShort(lstrcmpi(Share_GetName(pids1),
                                            Share_GetName(pids2)));
        }
        else
        {
            return ResultFromShort(-1);
        }
    }
    else if (Share_IsSpecial(pids2))
    {
        return ResultFromShort(1);
    }
#endif // WIZARDS

    // Documentation says iCol is always zero, but that is wrong! It will
    // be non-zero in case the user has clicked on a column heading to sort
    // the column. In general, we want the entire item to be equal before we
    // return 0 for equality. To do this, we first check the chosen element.
    // If it is not equal, return the value. Otherwise, check all elements in
    // this standard order:
    //      name
    //      comment
    //      path
    //      max uses
    //      current uses
    // Only after all these checks return 0 (equality) do we return 0 (equality)

    iCmp = _CompareOne(iCol, pids1, pids2);
    if (iCmp != 0)
    {
        return ResultFromShort(PlusMinus(iCmp));
    }

    // now, check each in turn

    iCmp = _CompareOne(ICOL2_NAME, pids1, pids2);
    if (iCmp != 0)
    {
        return ResultFromShort(PlusMinus(iCmp));
    }

    iCmp = _CompareOne(ICOL2_COMMENT, pids1, pids2);
    if (iCmp != 0)
    {
        return ResultFromShort(PlusMinus(iCmp));
    }

    if (This->m_level == 2)
    {
        iCmp = _CompareOne(ICOL2_PATH, pids1, pids2);
        if (iCmp != 0)
        {
            return ResultFromShort(PlusMinus(iCmp));
        }

        iCmp = _CompareOne(ICOL2_MAXUSES, pids1, pids2);
        if (iCmp != 0)
        {
            return ResultFromShort(PlusMinus(iCmp));
        }
    }

    return 0;   // the same!
}


STDMETHODIMP
CSharesSF::CreateViewObject(
    HWND hwnd,
    REFIID riid,
    LPVOID* ppvOut
    )
{
    CShares* This = IMPL(CShares,m_ShellFolder,this);
    HRESULT hr = E_NOINTERFACE;

    *ppvOut = NULL;

    if (IsEqualIID(riid, IID_IShellView))
    {
        CSFV csfv =
        {
            sizeof(CSFV),         // cbSize
            (IShellFolder*)this,  // pshf
            NULL,                 // psvOuter
            NULL,                 // pidl to monitor (NULL == all)
            SHCNE_NETSHARE | SHCNE_NETUNSHARE | SHCNE_UPDATEITEM, // events
            _SFVCallBack,         // pfnCallback
            FVM_DETAILS
        };

        hr = SHCreateShellFolderViewEx(&csfv, (LPSHELLVIEW *)ppvOut);
    }
    else if (IsEqualIID(riid, IID_IShellDetails))
    {
        appAssert(This->m_level != 0);
        CSharesSD* pObj = new CSharesSD(hwnd, This->m_level);
        if (NULL == pObj)
        {
            return E_OUTOFMEMORY;
        }

        hr = pObj->QueryInterface(riid, ppvOut);
        pObj->Release();
    }
    else if (IsEqualIID(riid, IID_IContextMenu))
    {
        // Create a context menu for the background
        CSharesCMBG* pObj = new CSharesCMBG(hwnd, This->m_pszMachine, This->m_level);
        if (NULL == pObj)
        {
            return E_OUTOFMEMORY;
        }

        hr = pObj->QueryInterface(riid, ppvOut);
        pObj->Release();
    }

    return hr;
}


STDMETHODIMP
CSharesSF::GetDisplayNameOf(
    LPCITEMIDLIST pidl,
    DWORD uFlags,
    LPSTRRET lpName
    )
{
    CShares* This = IMPL(CShares,m_ShellFolder,this);

    LPIDSHARE pids = (LPIDSHARE)pidl;
    if (uFlags == SHGDN_FORPARSING)
    {
        return E_NOTIMPL;   // don't support parsing.
    }
    else if (uFlags == SHGDN_INFOLDER)
    {
        return STRRETCopy(Share_GetName(pids), lpName);
    }
    else if (uFlags == SHGDN_NORMAL)
    {
        if (NULL == This->m_pszMachine)
        {
            return STRRETCopy(Share_GetName(pids), lpName);
        }
        else
        {
            LPWSTR pszMachine = This->m_pszMachine;
            if (pszMachine[0] == TEXT('\\') && pszMachine[1] == TEXT('\\'))
            {
                pszMachine += 2;
            }

            WCHAR szBuf[MAX_PATH];
            szBuf[0] = L'\0';
            MyFormatMessage(
                    MSG_TEMPLATE_WITH_ON,
                    szBuf,
                    ARRAYLEN(szBuf),
                    pszMachine,
                    Share_GetName(pids));
#ifdef UNICODE
            LPTSTR pszCopy = (LPTSTR)SHAlloc((lstrlen(szBuf)+1) * sizeof(TCHAR));
            if (pszCopy)
            {
                wcscpy(pszCopy, szBuf);
                lpName->uType = STRRET_OLESTR;
                lpName->pOleStr = pszCopy;
            }
            else
            {
                lpName->uType = STRRET_CSTR;
                lpName->cStr[0] = '\0';
            }
#else
            lpName->uType = STRRET_CSTR;
            lstrcpyn(lpName->cStr, szBuf, ARRAYSIZE(lpName->cStr));
            SHFree(pszRet);
#endif

            return S_OK;
        }
    }
    else
    {
        return E_INVALIDARG;
    }
}


STDMETHODIMP
CSharesSF::SetNameOf(
    HWND hwndOwner,
    LPCITEMIDLIST pidl,
    LPCOLESTR lpszName,
    DWORD uFlags,
    LPITEMIDLIST* ppidlOut
    )
{
    CShares* This = IMPL(CShares,m_ShellFolder,this);

    if (uFlags != SHGDN_INFOLDER)
    {
        return E_NOTIMPL;
    }

    if (NULL == lpszName || L'\0' == *lpszName)
    {
        // can't change name to nothing
        MessageBeep(0);
        return E_FAIL;
    }

    NET_API_STATUS ret;
    WCHAR szBuf[MAX_PATH];
    LPSHARE_INFO_502 pInfo;
    LPIDSHARE pids = (LPIDSHARE)pidl;

    // Get information about the existing share before deleting it.
    ret = NetShareGetInfo(This->m_pszMachine, Share_GetName(pids), 502, (LPBYTE*)&pInfo);
    if (ret != NERR_Success)
    {
        DisplayError(hwndOwner, IERR_CANT_DEL_SHARE, ret, Share_GetName(pids));
        return HRESULT_FROM_WIN32(ret);
    }

    // Validate the new share name

    // Trying to create a reserved share?
    if (   (0 == _wcsicmp(g_szIpcShare,   lpszName))
        || (0 == _wcsicmp(g_szAdminShare, lpszName)))
    {
        MyErrorDialog(hwndOwner, MSG_ADDSPECIAL2);
        NetApiBufferFree(pInfo);
        return E_FAIL;
    }

    HRESULT hrTemp;
    if (!IsValidShareName(lpszName, &hrTemp))
    {
        MyErrorDialog(hwndOwner, hrTemp);
        NetApiBufferFree(pInfo);
        return E_FAIL;
    }

    // Check to see that the same share isn't already used, for either the
    // same path or for another path.

    SHARE_INFO_2* pInfo2;
    ret = NetShareGetInfo(This->m_pszMachine, (LPWSTR)lpszName, 2, (LPBYTE*)&pInfo2);
    if (ret == NERR_Success)
    {
        // Is it already shared for the same path?
        if (0 == _wcsicmp(pInfo2->shi2_path, pInfo->shi502_path))
        {
            MyErrorDialog(hwndOwner, IERR_AlreadyExists, lpszName);
            NetApiBufferFree(pInfo);
            NetApiBufferFree(pInfo2);
            return TRUE;
        }

        // Shared for a different path. Ask the user if they wish to delete
        // the old share and create the new one using the name.

        DWORD id = ConfirmReplaceShare(hwndOwner, lpszName, pInfo2->shi2_path, pInfo->shi502_path);
        if (id == IDNO || id == IDCANCEL)   // BUGBUG: should be only yes/no
        {
            NetApiBufferFree(pInfo);
            NetApiBufferFree(pInfo2);
            return E_FAIL;
        }

        // User said to replace the old share. Do it.
        ret = NetShareDel(This->m_pszMachine, (LPWSTR)lpszName, 0);
        if (ret != NERR_Success)
        {
            DisplayError(hwndOwner, IERR_CANT_DEL_SHARE, ret, (LPWSTR)lpszName);
            NetApiBufferFree(pInfo);
            NetApiBufferFree(pInfo2);
            return FALSE;
        }
        else
        {
            SHChangeNotify(SHCNE_NETUNSHARE, SHCNF_PATH, pInfo2->shi2_path, NULL);
        }

        NetApiBufferFree(pInfo2);
    }

    // Check for downlevel accessibility
    ULONG nType;
    if (NERR_Success != NetpPathType(NULL, (LPWSTR)lpszName, &nType, INPT_FLAGS_OLDPATHS))
    {
        DWORD id = MyConfirmationDialog(
                        hwndOwner,
                        IERR_InaccessibleByDos,
                        MB_YESNO | MB_ICONEXCLAMATION,
                        lpszName);
        if (id == IDNO)
        {
            return TRUE;
        }
    }

    // delete the existing share
    ret = NetShareDel(This->m_pszMachine, Share_GetName(pids), 0);
    if (ret != NERR_Success)
    {
        NetApiBufferFree(pInfo);
        DisplayError(hwndOwner, IERR_CANT_DEL_SHARE, ret, Share_GetName(pids));
        return HRESULT_FROM_WIN32(ret);
    }

    // Create a new share with the new name.
    LPWSTR ptmp = pInfo->shi502_netname;
    pInfo->shi502_netname = (LPWSTR)lpszName;   // cast away const
    ret = NetShareAdd(This->m_pszMachine, 502, (LPBYTE)pInfo, NULL);
    if (ret != NERR_Success)
    {
        pInfo->shi502_netname = ptmp;
        NetApiBufferFree(pInfo);
        DisplayError(hwndOwner, IERR_CANT_ADD_SHARE, ret, (LPWSTR)lpszName); // cast away const
        return HRESULT_FROM_WIN32(ret);
    }

    // Ok, now I've renamed it. So, fill an ID list with the new guy, and
    // return it in *ppidlOut.

    HRESULT hr = S_OK;
    if (NULL != ppidlOut)
    {
        IDSHARE ids;
        FillID2(&ids, (LPSHARE_INFO_2)pInfo); // ignore security at end of level 502

        *ppidlOut = ILClone((LPCITEMIDLIST)(&ids));
        if (NULL == *ppidlOut)
        {
            hr = E_OUTOFMEMORY;
        }
    }

    // force a view refresh
    SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_IDLIST, NULL, 0);

    pInfo->shi502_netname = ptmp;
    NetApiBufferFree(pInfo);
    return hr;
}


//
// Callback from SHCreateShellFolderViewEx
//

HRESULT CALLBACK
CSharesSF::_SFVCallBack(
    LPSHELLVIEW psvOuter,
    LPSHELLFOLDER psf,
    HWND hwndOwner,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    CShares* This = IMPL(CShares,m_ShellFolder,psf);
    HRESULT hr = S_OK;     // assume no error

    switch (uMsg)
    {
    case DVM_UPDATESTATUSBAR:
    {
        IShellBrowser* psb = FileCabinet_GetIShellBrowser(hwndOwner);
        UINT cidl = ShellFolderView_GetSelectedCount(hwndOwner);
        if (cidl == 1)
        {
            LPITEMIDLIST *apidl;
            LPIDSHARE pids;
            LPTSTR lpsz = TEXT("");

            ShellFolderView_GetSelectedObjects(hwndOwner, &apidl);
            if (apidl)
            {
                pids = (LPIDSHARE)apidl[0];
                if (Share_IsShare(pids))
                {
                    lpsz = Share_GetComment(pids);
                }
                FSSetStatusText(hwndOwner, &lpsz, 0, 0);
                LocalFree(apidl);
            }
        }
        else
        {
            hr = E_FAIL;
        }
        break;
    }

    case DVM_MERGEMENU:
    {
        appDebugOut((DEB_TRACE, "DVM_MERGEMENU\n"));
        appAssert(This->m_pMenuBg == NULL);
        hr = psf->CreateViewObject(hwndOwner, IID_IContextMenu, (LPVOID*)&This->m_pMenuBg);
        if (SUCCEEDED(hr))
        {
            LPQCMINFO pqcm = (LPQCMINFO)lParam;
            hr = This->m_pMenuBg->QueryContextMenu(
                                    pqcm->hmenu,
                                    pqcm->indexMenu,
                                    pqcm->idCmdFirst,
                                    pqcm->idCmdLast,
                                    CMF_DVFILE);
        }
        break;
    }

    case DVM_UNMERGEMENU:
    {
        appDebugOut((DEB_TRACE, "DVM_UNMERGEMENU\n"));
        if (NULL != This->m_pMenuBg)
        {
            This->m_pMenuBg->Release();
            This->m_pMenuBg = NULL;
        }
        break;
    }

    case DVM_INVOKECOMMAND:
    {
        appDebugOut((DEB_TRACE, "DVM_INVOKECOMMAND\n"));
        appAssert(This->m_pMenuBg != NULL);
        CMINVOKECOMMANDINFO ici =
        {
            sizeof(ici),
            0,  // mask
            hwndOwner,
            (LPCSTR)wParam,
            NULL,
            NULL,
            0,
            0,
            NULL
        };
        hr = This->m_pMenuBg->InvokeCommand(&ici);
        break;
    }

    case DVM_GETHELPTEXT:
    {
        appDebugOut((DEB_TRACE, "DVM_GETHELPTEXT\n"));
        hr = This->m_pMenuBg->GetCommandString(LOWORD(wParam), GCS_HELPTEXT, NULL, (LPSTR)lParam, HIWORD(wParam));
        break;
    }

    case DVM_DEFITEMCOUNT:
        //
        // If DefView times out enumerating items, let it know we probably only
        // have about 20 items
        //

        *(int *)lParam = 20;
        break;

    case DVM_FSNOTIFY:
    {
        LPCITEMIDLIST* ppidl = (LPCITEMIDLIST*)wParam;
        LONG lEvent = lParam;

        switch (lEvent)
        {
        case SHCNE_NETSHARE:
        case SHCNE_NETUNSHARE:
            // a share was added, removed, or changed. Force a view refresh.
            SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_IDLIST, NULL, 0);
            return S_OK;
        }
        break;
    }

    default:
        hr = E_FAIL;
    }
    return hr;
}