// need IProgressDialog to be marshaled, def in .idl file (cleanup needed here)
// get CLSID_MediaDevMgr set to "Both"

// issues:
//  can't drop on open folder, Insert() throws an exception
//  Rio600 Next throws exception on cidl > 1
//  Storage2::GetStroage() fails for Rio600, does SP need to implement this?

#include "pch.h"
#include <shlobj.h>
#include <shsemip.h>
#include <shlobjp.h>    // IDelegateFolder
#include "thisdll.h"
#include <windowsx.h>
#include <varutil.h>
#include <dobjutil.h>
#include <malloc.h>
#include <dpa.h>
#include <shdguid.h>
#include "shdup.h"
#include "ids.h"
#include <ccstock2.h>
#pragma hdrstop
#include <wmsdk.h>
#include <mswmdm.h>
#include <scclient.h>
#include <mswmdm_i.c>   // get CLSID/IID defs

#include <ntquery.h>

#define DEFINE_SCID(name, fmtid, pid) const SHCOLUMNID name = { fmtid, pid }
#define IsEqualSCID(a, b)   (((a).pid == (b).pid) && IsEqualIID((a).fmtid, (b).fmtid) )

DEFINE_SCID(SCID_SIZE,              PSGUID_STORAGE, PID_STG_SIZE);
DEFINE_SCID(SCID_DESCRIPTIONID,     PSGUID_SHELLDETAILS, PID_DESCRIPTIONID);
DEFINE_SCID(SCID_CAPACITY,          PSGUID_VOLUME, PID_VOLUME_CAPACITY);
DEFINE_SCID(SCID_FREESPACE,         PSGUID_VOLUME, PID_VOLUME_FREE);
DEFINE_SCID(SCID_DisplayProperties, PSGUID_WEBVIEW, PID_DISPLAY_PROPERTIES);
DEFINE_SCID(SCID_TYPE,              PSGUID_STORAGE, PID_STG_STORAGETYPE);
DEFINE_SCID(SCID_NAME,              PSGUID_STORAGE, PID_STG_NAME);
DEFINE_SCID(SCID_WRITETIME,         PSGUID_STORAGE, PID_STG_WRITETIME);

DEFINE_SCID(SCID_AUDIO_Duration,    PSGUID_AUDIO, PIDASI_TIMELENGTH);       //100ns units, not milliseconds. VT_UI8, not VT_UI4
DEFINE_SCID(SCID_AUDIO_Bitrate,     PSGUID_AUDIO, PIDASI_AVG_DATA_RATE);    // bits per second


EXTERN_C const BYTE abPVK[4096];
EXTERN_C const BYTE abCert[100];

#pragma pack(1)
typedef struct              // typedef struct
{                           // {
// these need to line up -----------------------
    WORD cbSize;            //     WORD cbSize;                // Size of entire item ID
    WORD wOuter;            //     WORD wOuter;                // Private data owned by the outer folder
    WORD cbInner;           //     WORD cbInner;               // Size of delegate's data
// ---------------------------------------------
    DWORD dwMagic;          //     BYTE rgb[1];                // Inner folder's data,
    DWORD dwAttributes;     // } DELEGATEITEMID;
    ULARGE_INTEGER cbTotal;
    ULARGE_INTEGER cbFree;
    union 
    {
        FILETIME ftModified;
        ULARGE_INTEGER ulModified;
    };
    WCHAR szName[1]; // variable size
} MEDIADEVITEM;
#pragma pack()

typedef UNALIGNED MEDIADEVITEM * LPMEDIADEVITEM;
typedef const UNALIGNED MEDIADEVITEM * LPCMEDIADEVITEM;

#define MEDIADEVITEM_MAGIC 0x08311978

class CMediaDeviceFolder;
class CMediaDeviceEnum;
class CMediaDeviceDropTarget;

class CMediaDeviceFolder : public IShellFolder2, IPersistFolder2, IShellFolderViewCB, IDelegateFolder, IContextMenuCB
{
public:
    CMediaDeviceFolder(CMediaDeviceFolder *pParent, IWMDMStorage *pstg);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IPersist
    STDMETHODIMP GetClassID(CLSID *pClassID);

    // IPersistFolder
    STDMETHODIMP Initialize(LPCITEMIDLIST pidl);

    // IPersistFolder2
    STDMETHODIMP GetCurFolder(LPITEMIDLIST *ppidl);

    // IShellFolder
    STDMETHODIMP ParseDisplayName(HWND hwnd, LPBC pbc, LPOLESTR pszName, ULONG * pchEaten, LPITEMIDLIST * ppidl, ULONG *pdwAttributes);
    STDMETHODIMP EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList);
    STDMETHODIMP BindToObject(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppvOut);
    STDMETHODIMP BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv)
            { return BindToObject(pidl, pbc, riid, ppv); };
    STDMETHODIMP CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
    STDMETHODIMP CreateViewObject(HWND hwndOwner, REFIID riid, void **ppvOut);
    STDMETHODIMP GetAttributesOf(UINT cidl, LPCITEMIDLIST * apidl, ULONG *rgfInOut);
    STDMETHODIMP GetUIObjectOf(HWND hwndOwner, UINT cidl, LPCITEMIDLIST * apidl, REFIID riid, UINT * prgfInOut, void **ppvOut);
    STDMETHODIMP GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD uFlags, LPSTRRET lpName);
    STDMETHODIMP SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName, DWORD uFlags, LPITEMIDLIST * ppidlOut);

    // IShellFolder2
    STDMETHODIMP GetDefaultSearchGUID(GUID *pGuid)
            { return E_NOTIMPL; };
    STDMETHODIMP EnumSearches(IEnumExtraSearch **ppenum)
            { return E_NOTIMPL; };
    STDMETHODIMP GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
            { return E_NOTIMPL; };
    STDMETHODIMP GetDefaultColumnState(UINT iColumn, DWORD *pbState)
            { return E_NOTIMPL; }
    STDMETHODIMP GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv);
    STDMETHODIMP GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *pDetails);
    STDMETHODIMP MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid);

    // IShellFolderViewCB
    STDMETHODIMP MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam);

    // IDelegateFolder
    STDMETHODIMP SetItemAlloc(IMalloc *pmalloc);

    // IContextMenuCB
    STDMETHODIMP CallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam);

    // public for friends
    LPCITEMIDLIST GetIDList() { return _pidl; }

private:
    ~CMediaDeviceFolder();

    HRESULT _CreateIDList(LPCTSTR pszName, MEDIADEVITEM **ppmditem, BOOL *pfFolder);
    HRESULT _IDListForDevice(IWMDMDevice *pdev, LPITEMIDLIST *ppidl, BOOL *pfFolder);
    HRESULT _IDListForStorage(IWMDMStorage *pstg, LPITEMIDLIST *ppidl, BOOL *pfFolder);
    ULONG _GetAttributesOf(LPCMEDIADEVITEM pmdi, ULONG rgfIn);
    HRESULT _CreateExtractIcon(LPCMEDIADEVITEM pmdi, REFIID riid, void **ppv);
    static HRESULT CALLBACK _ItemsMenuCB(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam);
    HRESULT _DeleteItems(HWND hwnd, IDataObject *pdtobj);
    HRESULT _ChangeNotifyItem(LONG lEvent, LPCITEMIDLIST pidl);
    HRESULT _ChangeNotifyObj(LONG lEvent, IUnknown *punkStg);

    // folder view callback handlers
    HRESULT _OnBackgroundEnum(DWORD pv);
    HRESULT _OnGetNotify(DWORD pv, LPITEMIDLIST *ppidl, LONG *plEvents);
    HRESULT _OnViewMode(DWORD pv, FOLDERVIEWMODE *pvm);

    LPCMEDIADEVITEM _IsValid(LPCITEMIDLIST pidl);
    DWORD _IsFolder(LPCMEDIADEVITEM pmditem);
    BOOL _FileInfo(LPCMEDIADEVITEM pmdi, DWORD dwFlags, SHFILEINFO *psfi);
    LPCTSTR _DisplayName(LPCMEDIADEVITEM pmdi, LPTSTR pszName, UINT cch);
    LPCTSTR _Name(LPCMEDIADEVITEM pmdi, LPTSTR psz, UINT cch);
    LPCTSTR _RawName(LPCMEDIADEVITEM pmdi, LPTSTR psz, UINT cch);
    LPCTSTR _Type(LPCMEDIADEVITEM pmdi, LPTSTR psz, UINT cch);
    HRESULT _GetMediaDevMgr(REFIID riid, void **ppv);
    HRESULT _Authenticate(IUnknown *punk);
    HRESULT _EnumDevices(IWMDMEnumDevice **ppEnumDevices);
    HRESULT _FindDeviceByName(LPCWSTR pszName, IWMDMDevice **ppdevice);
    HRESULT _StorageByName(LPCWSTR pszName, REFIID riid, void **ppv);
    HRESULT _StroageFromItem(LPCMEDIADEVITEM pmdi, REFIID riid, void **ppv);
    HRESULT _StorageForDevice(IWMDMDevice *pdev, REFIID riid, void **ppv);
    HRESULT _Storage(REFIID riid, void **ppv);
    HRESULT _EnumStorage(IWMDMEnumStorage **ppEnumStorage);
    HRESULT _DeviceFromItem(LPCMEDIADEVITEM pmdi, IWMDMDevice **ppdev);
    BOOL _IsRoot();
    void *_Alloc(SIZE_T cb);
    BOOL _ShouldShowDevice(IWMDMDevice *pdev);

    friend CMediaDeviceEnum;
    friend CMediaDeviceDropTarget;

    LONG _cRef;
    IMalloc *_pmalloc;
    LPITEMIDLIST _pidl;             // full idlist of this folder
    BOOL _bRoot;                    // is this the root of the name space
    IGlobalInterfaceTable *_pgit;
    DWORD _dwStorageCookie;         // GIT token for IWMDMStorage
    DWORD _dwDevMgrCookie;          // GIT token for media device manager
    CSecureChannelClient *_pSAC;    // this must outlive any objects that used this

    BOOL _fDelegate;
};

class CMediaDeviceEnum : public IEnumIDList
{
public:
    CMediaDeviceEnum(CMediaDeviceFolder* prf, DWORD grfFlags);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IEnumIDList
    STDMETHODIMP Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched);
    STDMETHODIMP Skip(ULONG celt) { return E_NOTIMPL; };
    STDMETHODIMP Reset() { _iIndex = 0; return S_OK; };
    STDMETHODIMP Clone(IEnumIDList **ppenum) { return E_NOTIMPL; };

private:
    ~CMediaDeviceEnum();
    HRESULT _Init();
    BOOL _FilterItem(BOOL bFolder);

    LONG _cRef;
    CDPA<ITEMIDLIST> _dpaItems;
    int _iIndex;
    CMediaDeviceFolder *_pmdf;
    DWORD _grfFlags;
};

class CMediaDeviceDropTarget : public IDropTarget, INamespaceWalkCB, IWMDMProgress2
{
public:
    CMediaDeviceDropTarget(CMediaDeviceFolder *pFolder, HWND hwnd);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
    STDMETHODIMP_(ULONG) AddRef(void);
    STDMETHODIMP_(ULONG) Release(void);

    // IDropTarget
    STDMETHODIMP DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
    STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
    STDMETHODIMP DragLeave();
    STDMETHODIMP Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);

    // IWMDMProgress
    STDMETHODIMP Begin(DWORD dwEstimatedTicks);
    STDMETHODIMP Progress(DWORD dwTranspiredTicks);
    STDMETHODIMP End();

    // IWMDMProgress2
    STDMETHODIMP End2(HRESULT hrCompletionCode);

    // INamespaceWalkCB
    STDMETHODIMP FoundItem(IShellFolder *psf, LPCITEMIDLIST pidl);
    STDMETHODIMP EnterFolder(IShellFolder *psf, LPCITEMIDLIST pidl);
    STDMETHODIMP LeaveFolder(IShellFolder *psf, LPCITEMIDLIST pidl);
    STDMETHOD(InitializeProgressDialog)(LPWSTR *ppszTitle, LPWSTR *ppszCancel)
        { *ppszTitle = NULL; *ppszCancel = NULL; return E_NOTIMPL; }

private:
    ~CMediaDeviceDropTarget();
    DWORD _GetDropEffect(DWORD *pdwEffect, DWORD grfKeyState);
    static DWORD CALLBACK _DoCopyThreadProc(void *pv);
    HRESULT _DoCopy(IDataObject *pdtobj);

    LONG _cRef;

    CMediaDeviceFolder *_pmdf;
    HWND _hwnd;                     // EVIL: used as a site and UI host
    IDataObject *_pdtobj;           // used durring DragOver() and DoDrop(), don't use on background thread

    UINT _idCmd;
    DWORD _grfKeyStateLast;         // for previous DragOver/Enter
    DWORD _dwEffectLastReturned;    // stashed effect that's returned by base class's dragover
    DWORD _dwEffectPreferred;       // if dwData & DTID_PREFERREDEFFECT

    IProgressDialog *_ppd;
    ULONGLONG _ulProgressTotal;
    ULONGLONG _ulProgressCurrent;
    ULONGLONG _ulThisFile;
    IUnknown *_punkFTM;             // make our callback interface calls direct
};

CMediaDeviceFolder::CMediaDeviceFolder(CMediaDeviceFolder *pParent, IWMDMStorage *pstg) : 
    _cRef(1), _pidl(NULL), _dwStorageCookie(-1), _dwDevMgrCookie(-1)
{
    _bRoot = (NULL == pstg);

    if (SUCCEEDED(CoCreateInstance(CLSID_StdGlobalInterfaceTable, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IGlobalInterfaceTable, &_pgit))))
    {
        if (pstg)
        {
            _pgit->RegisterInterfaceInGlobal(pstg, IID_IWMDMStorage, &_dwStorageCookie);
        }
    }
    DllAddRef();
}

CMediaDeviceFolder::~CMediaDeviceFolder()
{
    ILFree(_pidl);
#ifdef _X86_
    if (_pSAC)
        delete _pSAC;
#endif

    if (_pgit)
    {
        if (-1 != _dwStorageCookie)
            _pgit->RevokeInterfaceFromGlobal(_dwStorageCookie);

        if (-1 != _dwDevMgrCookie)
            _pgit->RevokeInterfaceFromGlobal(_dwDevMgrCookie);
        _pgit->Release();
    }

    ATOMICRELEASE(_pmalloc);

    DllRelease();
}

HRESULT CMediaDeviceFolder::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENTMULTI(CMediaDeviceFolder, IShellFolder,      IShellFolder2),
        QITABENT     (CMediaDeviceFolder, IShellFolder2),
        QITABENTMULTI(CMediaDeviceFolder, IPersist,          IPersistFolder2),
        QITABENTMULTI(CMediaDeviceFolder, IPersistFolder,    IPersistFolder2),
        QITABENT     (CMediaDeviceFolder, IPersistFolder2),
        QITABENT     (CMediaDeviceFolder, IShellFolderViewCB),
        QITABENT     (CMediaDeviceFolder, IDelegateFolder),
        QITABENT     (CMediaDeviceFolder, IContextMenuCB),
        { 0 },
    };
    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) CMediaDeviceFolder::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CMediaDeviceFolder::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

STDAPI CMediaDeviceFolder_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi)
{
    HRESULT hr;
    CMediaDeviceFolder *pmdf = new CMediaDeviceFolder(NULL, NULL);
    if (pmdf)
    {
        *ppunk = SAFECAST(pmdf, IShellFolder2 *);
        hr = S_OK;
    }
    else
    {
        *ppunk = NULL;
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_Authenticate(IUnknown *punk)
{
#ifndef _X86_
    HRESULT hr = E_FAIL;
#else
    IComponentAuthenticate *pAuth;
    HRESULT hr = punk->QueryInterface(IID_PPV_ARG(IComponentAuthenticate, &pAuth));
    if (SUCCEEDED(hr))
    {
        if (NULL == _pSAC)
            _pSAC = new CSecureChannelClient;
        if (_pSAC)
        {
            hr = _pSAC->SetCertificate(SAC_CERT_V1, (BYTE *)abCert, sizeof(abCert), (BYTE *)abPVK,  sizeof(abPVK));
            if (SUCCEEDED(hr))
            {
                _pSAC->SetInterface(pAuth);
                hr = _pSAC->Authenticate(SAC_PROTOCOL_V1);
            }
        }
        else
            hr = E_OUTOFMEMORY;

        pAuth->Release();
    }
#endif
    return hr;
}

HRESULT CMediaDeviceFolder::_GetMediaDevMgr(REFIID riid, void **ppv)
{
    *ppv = NULL;
    HRESULT hr = E_FAIL;
    if (_IsRoot() && _pgit)
    {
        if (-1 == _dwDevMgrCookie)
        {
            IWMDeviceManager *pdevmgr;
            if (SUCCEEDED(CoCreateInstance(CLSID_MediaDevMgr, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IWMDeviceManager, &pdevmgr))))
            {
                _Authenticate(pdevmgr);
                _pgit->RegisterInterfaceInGlobal(pdevmgr, IID_IWMDeviceManager, &_dwDevMgrCookie);
                pdevmgr->Release();
            }
        }

        if (-1 != _dwDevMgrCookie)
            hr = _pgit->GetInterfaceFromGlobal(_dwDevMgrCookie, riid, ppv);
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_EnumDevices(IWMDMEnumDevice **ppEnumDevices)
{
    *ppEnumDevices = NULL;

    IWMDeviceManager *pdevmgr;
    HRESULT hr = _GetMediaDevMgr(IID_PPV_ARG(IWMDeviceManager, &pdevmgr));
    if (SUCCEEDED(hr))
    {
        hr = pdevmgr->EnumDevices(ppEnumDevices);
        pdevmgr->Release();
    }
    return hr;
}

// test to see if this is the root of the media devices name space (where the devices are)
BOOL CMediaDeviceFolder::_IsRoot()
{
    return _bRoot;
}

HRESULT CMediaDeviceFolder::_Storage(REFIID riid, void **ppv)
{
    *ppv = NULL;
    HRESULT hr = E_NOINTERFACE;
    if (-1 != _dwStorageCookie)
    {
        ASSERT(!_IsRoot());
        hr = _pgit->GetInterfaceFromGlobal(_dwStorageCookie, riid, ppv);
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_EnumStorage(IWMDMEnumStorage **ppEnumStorage)
{
    *ppEnumStorage = NULL;

    IWMDMStorage *pStorage;
    HRESULT hr = _Storage(IID_PPV_ARG(IWMDMStorage, &pStorage));
    if (SUCCEEDED(hr))
    {
        hr = pStorage->EnumStorage(ppEnumStorage);
        pStorage->Release();
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_FindDeviceByName(LPCWSTR pszName, IWMDMDevice **ppdevice)
{
    IWMDMEnumDevice *pEnumDevice;
    HRESULT hr = _EnumDevices(&pEnumDevice);
    if (SUCCEEDED(hr))
    {
        hr = E_FAIL;
        ULONG ulFetched;
        IWMDMDevice *pdev;
        while (S_OK == pEnumDevice->Next(1, &pdev, &ulFetched))
        {
            WCHAR szName[MAX_PATH];
            if (SUCCEEDED(pdev->GetName(szName, ARRAYSIZE(szName))) &&
                (0 == StrCmpIC(pszName, szName)))
            {
                *ppdevice = pdev;
                hr = S_OK;
                break;
            }
            pdev->Release();
        }
        pEnumDevice->Release();
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_StorageByName(LPCWSTR pszName, REFIID riid, void **ppv)
{
    IWMDMStorage2 *pstg;
    HRESULT hr = _Storage(IID_PPV_ARG(IWMDMStorage2, &pstg));
    if (SUCCEEDED(hr))
    {
        IWMDMStorage *pstgFound;
        hr = pstg->GetStorage((LPWSTR)pszName, &pstgFound);
        if (SUCCEEDED(hr))
        {
            hr = pstgFound->QueryInterface(riid, ppv);
            pstgFound->Release();
        }
        else if (E_NOINTERFACE == hr)
        {
            // Rio600 fails on this now. I asked jhelin to look into this
            IWMDMEnumStorage *penum;
            if (SUCCEEDED(pstg->EnumStorage(&penum)))
            {
                ULONG ulFetched;
                while (S_OK == penum->Next(1, &pstgFound, &ulFetched))
                {
                    WCHAR szName[MAX_PATH];
                    if (SUCCEEDED(pstgFound->GetName(szName, ARRAYSIZE(szName))) &&
                        (0 == StrCmpI(pszName, szName)))
                    {
                        hr = pstgFound->QueryInterface(riid, ppv);
                    }
                    pstgFound->Release();

                    if (SUCCEEDED(hr))
                        break;
                }
                penum->Release();
            }
        }
        pstg->Release();
    }
    return hr;
}

LPCMEDIADEVITEM CMediaDeviceFolder::_IsValid(LPCITEMIDLIST pidl)
{
    if (pidl && ((LPCMEDIADEVITEM)pidl)->dwMagic == MEDIADEVITEM_MAGIC)
        return (LPCMEDIADEVITEM)pidl;
    return NULL;
}

DWORD CMediaDeviceFolder::_IsFolder(LPCMEDIADEVITEM pmditem) 
{ 
    return pmditem->dwAttributes & WMDM_FILE_ATTR_FOLDER ? FILE_ATTRIBUTE_DIRECTORY : 0; 
}

// helper to support being run as a delegate or a stand alone folder
//
// the cbInner is the size of the data needed by the delegate. we need to compute
// the full size of the pidl for the allocation and init that we the outer folder data
void *CMediaDeviceFolder::_Alloc(SIZE_T cbInner)
{
    DELEGATEITEMID *pidl;
    if (_pmalloc)
    {
        pidl = (DELEGATEITEMID *)_pmalloc->Alloc(cbInner);
    }
    else
    {
        SIZE_T cbAlloc = 
            sizeof(DELEGATEITEMID) - sizeof(pidl->rgb[0]) + // header
            cbInner +                                       // inner
            sizeof(WORD);                                   // trailing null (pidl terminator)

        pidl = (DELEGATEITEMID *)SHAlloc(cbAlloc);
        if (pidl)
        {
            ZeroMemory(pidl, cbAlloc);              // make it all empty
            pidl->cbSize = (WORD)cbAlloc - sizeof(WORD);
            pidl->cbInner = (WORD)cbInner;
        }
    }
    return (void *)pidl;
}

HRESULT CMediaDeviceFolder::_CreateIDList(LPCTSTR pszName, MEDIADEVITEM **ppmditem, BOOL *pfFolder)
{
    HRESULT hr;
    UINT cbInner = sizeof(MEDIADEVITEM) - (sizeof(DELEGATEITEMID) - 1) + 
        (sizeof(WCHAR) * lstrlen(pszName)); // null is accounted for in szName[1]
    *ppmditem = (MEDIADEVITEM *)_Alloc(cbInner);
    if (*ppmditem)
    {
        (*ppmditem)->dwMagic = MEDIADEVITEM_MAGIC;
        StrCpyW((*ppmditem)->szName, pszName);   
        if (pfFolder)
            *pfFolder = _IsFolder(*ppmditem);
        hr = S_OK;
    }
    else
        hr = E_OUTOFMEMORY;
    return hr;
}

// Creates an item identifier list for the objects in the namespace

HRESULT CMediaDeviceFolder::_IDListForDevice(IWMDMDevice *pdev, LPITEMIDLIST *ppidl, BOOL *pfFolder)
{
    WCHAR szName[MAX_PATH];
    HRESULT hr = pdev->GetName(szName, ARRAYSIZE(szName));
    if (SUCCEEDED(hr))
    {
        // WCHAR szTemp[128];
        // pdev->GetManufacturer(szTemp, ARRAYSIZE(szTemp));

        MEDIADEVITEM *pmditem;
        hr = _CreateIDList(szName, &pmditem, pfFolder);
        if (SUCCEEDED(hr))
        {
            IWMDMStorage *pstg;

            if (SUCCEEDED(_StorageForDevice(pdev, IID_PPV_ARG(IWMDMStorage, &pstg))))
            {
                IWMDMStorageGlobals *pstgGlobals;
                if (SUCCEEDED(pstg->GetStorageGlobals(&pstgGlobals)))
                {
                    pstgGlobals->GetTotalSize(&pmditem->cbTotal.LowPart, &pmditem->cbTotal.HighPart);
                    pstgGlobals->GetTotalFree(&pmditem->cbFree.LowPart, &pmditem->cbFree.HighPart);

                    pstgGlobals->Release();
                }
                pstg->Release();
            }

            pmditem->dwAttributes = WMDM_FILE_ATTR_FOLDER | WMDM_STORAGE_ATTR_HAS_FOLDERS;
            // pdev->GetType(&pmditem->dwType); // this is an expensive call, hits the disk
            *ppidl = (LPITEMIDLIST)pmditem;
        }
    }
    return hr;
}

void WMDMDateTimeToSystemTime(const WMDMDATETIME *pWMDM, FILETIME *pft)
{
    SYSTEMTIME st = {0};
    st.wYear           = pWMDM->wYear; 
    st.wMonth          = pWMDM->wMonth; 
    st.wDay            = pWMDM->wDay; 
    st.wHour           = pWMDM->wHour;
    st.wMinute         = pWMDM->wMinute; 
    st.wSecond         = pWMDM->wSecond; 

    SystemTimeToFileTime(&st, pft);
}

HRESULT CMediaDeviceFolder::_IDListForStorage(IWMDMStorage *pstg, LPITEMIDLIST *ppidl, BOOL *pfFolder)
{
    WCHAR szName[MAX_PATH];
    HRESULT hr = pstg->GetName(szName, ARRAYSIZE(szName));
    if (SUCCEEDED(hr))
    {
        MEDIADEVITEM *pmditem;
        hr = _CreateIDList(szName, &pmditem, pfFolder);
        if (SUCCEEDED(hr))
        {
            _WAVEFORMATEX fmt;
            pstg->GetAttributes(&pmditem->dwAttributes, &fmt);
 
            WMDMDATETIME datetime;
            if (SUCCEEDED(pstg->GetDate(&datetime)))
            {
                WMDMDateTimeToSystemTime(&datetime, &pmditem->ftModified);
            }

            if (!(pmditem->dwAttributes & WMDM_FILE_ATTR_FOLDER))
            {
                pstg->GetSize(&pmditem->cbTotal.LowPart, &pmditem->cbTotal.HighPart);
            }
            *ppidl = (LPITEMIDLIST)pmditem;
        }
    }
    return hr;
}

// IPersist

STDMETHODIMP CMediaDeviceFolder::GetClassID(CLSID *pClassID) 
{ 
    *pClassID = CLSID_MediaDeviceFolder; 
    return S_OK; 
}

// IPersistFolder

STDMETHODIMP CMediaDeviceFolder::Initialize(LPCITEMIDLIST pidl)
{
    ILFree(_pidl);
    return SHILClone(pidl, &_pidl); 
}

// IPersistFolder2

HRESULT CMediaDeviceFolder::GetCurFolder(LPITEMIDLIST *ppidl)
{
    if (_pidl)
        return SHILClone(_pidl, ppidl);

    *ppidl = NULL;
    return S_FALSE;
}


// IShellFolder(2)

HRESULT CMediaDeviceFolder::ParseDisplayName(HWND hwnd, LPBC pbc, LPOLESTR pszName, ULONG *pchEaten, LPITEMIDLIST *ppidl, ULONG *pdwAttributes)
{
    HRESULT hr;

    if (!pszName || !ppidl)
        return E_INVALIDARG;

    *ppidl = NULL;

#if 1
    hr = E_NOTIMPL;
#else
    TCHAR szName[MAX_PATH];
    hr = _NextSegment((LPCWSTR*)&pszName, szName, ARRAYSIZE(szName), TRUE);
    if (SUCCEEDED(hr))
    {
        hr = _IDListForDevice(szName, ppidl, NULL);
        if (SUCCEEDED(hr) && pszName)
        {
            IShellFolder *psf;
            hr = BindToObject(*ppidl, pbc, IID_PPV_ARG(IShellFolder, &psf));
            if (SUCCEEDED(hr))
            {
                ULONG chEaten;
                LPITEMIDLIST pidlNext;
                hr = psf->ParseDisplayName(hwnd, pbc, pszName, &chEaten, &pidlNext, pdwAttributes);
                if (SUCCEEDED(hr))
                    hr = SHILAppend(pidlNext, ppidl);
                psf->Release();
            }
        }
        else if (SUCCEEDED(hr) && pdwAttributes && *pdwAttributes)
        {
            GetAttributesOf(1, (LPCITEMIDLIST *)ppidl, pdwAttributes);
        }
    }
#endif

    // clean up if the parse failed.

    if (FAILED(hr))
    {
        ILFree(*ppidl);
        *ppidl = NULL;
    }

    return hr;
}

HRESULT CMediaDeviceFolder::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenum)
{
    HRESULT hr;
    CMediaDeviceEnum *penum = new CMediaDeviceEnum(this, grfFlags);
    if (penum)
    {
        hr = penum->QueryInterface(IID_PPV_ARG(IEnumIDList, ppenum));
        penum->Release();
    }
    else
    {
        hr = E_OUTOFMEMORY;
        *ppenum = NULL;
    }
    return hr;
}


STDAPI_(LPITEMIDLIST) ILCombineParentAndFirst(LPCITEMIDLIST pidlParent, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlNext)
{
    ULONG cbParent = ILGetSize(pidlParent);
    ULONG cbRest   = (ULONG)((ULONG_PTR)pidlNext - (ULONG_PTR)pidl);
    LPITEMIDLIST pidlNew = _ILCreate(cbParent + cbRest);
    if (pidlNew)
    {
        cbParent -= sizeof(pidlParent->mkid.cb);
        memcpy(pidlNew, pidlParent, cbParent);
        memcpy((BYTE *)pidlNew + cbParent, pidl, cbRest);
        ASSERT(_ILSkip(pidlNew, cbParent + cbRest)->mkid.cb == 0);
    }
    return pidlNew;
}

HRESULT CMediaDeviceFolder::_StorageForDevice(IWMDMDevice *pdev, REFIID riid, void **ppv)
{
    *ppv = NULL;
    IWMDMEnumStorage *penum;
    HRESULT hr = pdev->EnumStorage(&penum);
    if (SUCCEEDED(hr))
    {
        ULONG ulFetched;
        IWMDMStorage *pstg;
        if (S_OK == penum->Next(1, &pstg, &ulFetched))
        {
            hr= pstg->QueryInterface(riid, ppv);
            pstg->Release();
        }
        else
        {
            hr = E_NOINTERFACE;
        }
        penum->Release();
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_StroageFromItem(LPCMEDIADEVITEM pmdi, REFIID riid, void **ppv)
{
    WCHAR szName[MAX_PATH];
    _Name(pmdi, szName, ARRAYSIZE(szName));

    *ppv = NULL;
    HRESULT hr;
    if (_IsRoot())
    {
        // root case
        IWMDMDevice *pdev;
        hr = _FindDeviceByName(szName, &pdev);
        if (SUCCEEDED(hr))
        {
            hr = _StorageForDevice(pdev, riid, ppv);
            pdev->Release();
        }
    }
    else
    {
        hr = _StorageByName(szName, riid, ppv);
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_ChangeNotifyItem(LONG lEvent, LPCITEMIDLIST pidl)
{
    ITEMIDLIST *pidlFull;
    HRESULT hr = SHILCombine(_pidl, pidl, &pidlFull);
    if (SUCCEEDED(hr))
    {
        SHChangeNotify(lEvent, 0, (LPCTSTR)pidlFull, NULL);
        ILFree(pidlFull);
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_ChangeNotifyObj(LONG lEvent, IUnknown *punkStg)
{
    IWMDMStorage *pstg;
    HRESULT hr = punkStg->QueryInterface(IID_PPV_ARG(IWMDMStorage, &pstg));
    if (SUCCEEDED(hr))
    {
        ITEMIDLIST *pidl;
        BOOL bFolder;
        hr = _IDListForStorage(pstg, &pidl, &bFolder);
        if (SUCCEEDED(hr))
        {
            hr = _ChangeNotifyItem(lEvent, pidl);
            ILFree(pidl);
        }
        pstg->Release();
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_DeviceFromItem(LPCMEDIADEVITEM pmdi, IWMDMDevice **ppdev)
{
    *ppdev = NULL;

    TCHAR szName[MAX_PATH];
    return _IsRoot() ? _FindDeviceByName(_RawName(pmdi, szName, ARRAYSIZE(szName)), ppdev) : E_NOINTERFACE;
}

HRESULT CMediaDeviceFolder::BindToObject(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv)
{
    *ppv = NULL;

    HRESULT hr = E_NOINTERFACE;
    LPCMEDIADEVITEM pmdi = _IsValid(pidl);
    if (pmdi && _IsFolder(pmdi))
    {
        if (IID_IShellFolder == riid ||
            IID_IShellFolder2 == riid)
        {
            IWMDMStorage *pstg;
            hr = _StroageFromItem(pmdi, IID_PPV_ARG(IWMDMStorage, &pstg));
            if (SUCCEEDED(hr))
            {
                LPCITEMIDLIST pidlNext = _ILNext(pidl);
                LPITEMIDLIST pidlSubFolder = ILCombineParentAndFirst(_pidl, pidl, pidlNext);
                if (pidlSubFolder)
                {
                    CMediaDeviceFolder *pmdf = new CMediaDeviceFolder(this, pstg);
                    if (pmdf)
                    {
                        hr = pmdf->Initialize(pidlSubFolder);
                        if (SUCCEEDED(hr))
                        {
                            // if there's nothing left in the pidl, get the interface on this one.
                            if (ILIsEmpty(pidlNext))
                                hr = pmdf->QueryInterface(riid, ppv);
                            else
                            {
                                // otherwise, hand the rest of it off to the new shellfolder.
                                hr = pmdf->BindToObject(pidlNext, pbc, riid, ppv);
                            }
                        }
                        pmdf->Release();
                    }
                    else
                        hr = E_OUTOFMEMORY;

                    ILFree(pidlSubFolder);
                }
                else
                    hr = E_OUTOFMEMORY;

                pstg->Release();
            }
        }
    }
    else
        hr = E_FAIL;

    ASSERT((SUCCEEDED(hr) && *ppv) || (FAILED(hr) && (NULL == *ppv)));   // Assert hr is consistent w/out param.
    return hr;
}

enum
{
    DEV_COL_NAME = 0,
    DEV_COL_SIZE,
    DEV_COL_TYPE,
    DEV_COL_MODIFIED,
};

HRESULT CMediaDeviceFolder::CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
    LPCMEDIADEVITEM pmdi1 = _IsValid(pidl1);
    LPCMEDIADEVITEM pmdi2 = _IsValid(pidl2);
    int nCmp = 0;

    if (!pmdi1 || !pmdi2)
        return E_INVALIDARG;

    // folders always sort to the top of the list, if either of these items
    // are folders then compare on the folderness
    
    if (_IsFolder(pmdi1) || _IsFolder(pmdi2))
    {
        if (_IsFolder(pmdi1) && !_IsFolder(pmdi2))
            nCmp = -1;
        else if (!_IsFolder(pmdi1) && _IsFolder(pmdi2))
            nCmp = 1;
    }

    // if they match (or are not folders, then lets compare based on the column ID.

    if (nCmp == 0)
    {
        switch (lParam & SHCIDS_COLUMNMASK)
        {
        case DEV_COL_NAME:          // caught later on
            break;

        case DEV_COL_SIZE:
            if (pmdi1->cbTotal.QuadPart < pmdi2->cbTotal.QuadPart)
                nCmp = -1;
            else if (pmdi1->cbTotal.QuadPart > pmdi2->cbTotal.QuadPart)
                nCmp = 1;
            break;
 
        case DEV_COL_TYPE:
            {
                TCHAR szType1[MAX_PATH], szType2[MAX_PATH];
                _Type(pmdi1, szType1, ARRAYSIZE(szType1));
                _Type(pmdi2, szType2, ARRAYSIZE(szType2));
                nCmp = StrCmp(szType1, szType2);
                break;
            }

        case DEV_COL_MODIFIED:
            if (pmdi1->ulModified.QuadPart < pmdi2->ulModified.QuadPart)
                nCmp = -1;
            else if (pmdi1->ulModified.QuadPart > pmdi2->ulModified.QuadPart)
                nCmp = 1;
            break;
        }

        if (nCmp == 0)
        {
            TCHAR szName1[MAX_PATH], szName2[MAX_PATH];
            nCmp = StrCmpLogicalW(_Name(pmdi1, szName1, ARRAYSIZE(szName1)), _Name(pmdi2, szName2, ARRAYSIZE(szName2)));
        }
    }
    
    return ResultFromShort(nCmp);
}

HRESULT CMediaDeviceFolder::_OnBackgroundEnum(DWORD pv) 
{ 
    return S_OK;    // do enum on backgound always
}

HRESULT CMediaDeviceFolder::_OnGetNotify(DWORD pv, LPITEMIDLIST *ppidl, LONG *plEvents)
{
    *ppidl = _pidl; // evil alais
    *plEvents = SHCNE_RENAMEITEM | SHCNE_RENAMEFOLDER | \
                SHCNE_CREATE | SHCNE_DELETE | SHCNE_UPDATEDIR | SHCNE_UPDATEITEM | \
                SHCNE_MKDIR | SHCNE_RMDIR;
    return S_OK;
}

HRESULT CMediaDeviceFolder::_OnViewMode(DWORD pv, FOLDERVIEWMODE *pvm)
{
    *pvm = FVM_TILE;
    return S_OK;
}

// IShellFolderViewCB
STDMETHODIMP CMediaDeviceFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr = E_FAIL;
    switch (uMsg)
    {
    HANDLE_MSG(0, SFVM_BACKGROUNDENUM, _OnBackgroundEnum);
    HANDLE_MSG(0, SFVM_GETNOTIFY, _OnGetNotify);
    HANDLE_MSG(0, SFVM_DEFVIEWMODE, _OnViewMode);
    }
    return hr;
}

HRESULT CMediaDeviceFolder::CreateViewObject(HWND hwnd, REFIID riid, void **ppv)
{
    HRESULT hr = E_NOINTERFACE;
    *ppv = NULL;
    
    if (IsEqualIID(riid, IID_IShellView))
    {
        SFV_CREATE sSFV = { 0 };
        sSFV.cbSize = sizeof(sSFV);
        sSFV.psfvcb = SAFECAST(this, IShellFolderViewCB *);
        sSFV.pshf = SAFECAST(this, IShellFolder *);
        hr = SHCreateShellFolderView(&sSFV, (IShellView**)ppv);
    }
    else if (IsEqualIID(riid, IID_IContextMenu))
    {
        HKEY hkNoFiles = NULL;
        
        RegOpenKey(HKEY_CLASSES_ROOT, TEXT("Directory\\Background"), &hkNoFiles);

        hr = CDefFolderMenu_Create2(_pidl, hwnd, 0, NULL, this, NULL,
                                    1, &hkNoFiles,  (IContextMenu **)ppv);
        if (hkNoFiles)
            RegCloseKey(hkNoFiles);
    }
    else if (IsEqualIID(riid, IID_IDropTarget) && !_IsRoot())
    {
        CMediaDeviceDropTarget *psdt = new CMediaDeviceDropTarget(this, hwnd);
        if (psdt)
        {
            hr = psdt->QueryInterface(riid, ppv);
            psdt->Release();
        }
        else
            hr = E_OUTOFMEMORY;
    }

    return hr;
}

DWORD WMDMAttributesSFGAO(DWORD dwWMDMAttributes)
{
    DWORD dwShell = SFGAO_DROPTARGET;	// defaults

    if (WMDM_FILE_ATTR_FOLDER & dwWMDMAttributes)
        dwShell |= SFGAO_FOLDER;
    if (WMDM_FILE_ATTR_LINK & dwWMDMAttributes)
        dwShell |= SFGAO_LINK;
    if (WMDM_FILE_ATTR_CANDELETE & dwWMDMAttributes)
        dwShell |= SFGAO_CANDELETE;
    if (WMDM_FILE_ATTR_CANMOVE & dwWMDMAttributes)
        dwShell |= SFGAO_CANMOVE;
    if (WMDM_FILE_ATTR_CANRENAME & dwWMDMAttributes)
        dwShell |= SFGAO_CANRENAME;
    if (WMDM_FILE_ATTR_HIDDEN & dwWMDMAttributes)
        dwShell |= SFGAO_HIDDEN;
    if (WMDM_FILE_ATTR_READONLY & dwWMDMAttributes)
        dwShell |= SFGAO_READONLY;
    if (WMDM_STORAGE_ATTR_HAS_FOLDERS & dwWMDMAttributes)
        dwShell |= SFGAO_HASSUBFOLDER;
    return dwShell;
}

ULONG CMediaDeviceFolder::_GetAttributesOf(LPCMEDIADEVITEM pmdi, ULONG rgfIn)
{
    ULONG lResult = rgfIn & WMDMAttributesSFGAO(pmdi->dwAttributes);
    if (_IsRoot())
    {
        lResult |= SFGAO_CANLINK | SFGAO_REMOVABLE;
    }
    return lResult;
}

BOOL IsSelf(UINT cidl, LPCITEMIDLIST *apidl)
{
    return cidl == 0 || (cidl == 1 && (apidl == NULL || apidl[0] == NULL || ILIsEmpty(apidl[0])));
}

HRESULT CMediaDeviceFolder::GetAttributesOf(UINT cidl, LPCITEMIDLIST *apidl, ULONG *prgfInOut)
{
    UINT rgfOut = *prgfInOut;

    // return attributes of the namespace root?

    if (IsSelf(cidl, apidl))
    {
        rgfOut &= SFGAO_FOLDER | 
                  SFGAO_LINK | SFGAO_DROPTARGET |
                  SFGAO_CANRENAME | SFGAO_CANDELETE |
                  SFGAO_CANLINK | SFGAO_CANCOPY | 
                  SFGAO_CANMOVE | SFGAO_HASSUBFOLDER;
    }
    else
    {
        for (UINT i = 0; i < cidl; i++)
        {
            LPCMEDIADEVITEM pmdi = _IsValid(apidl[i]);
            if (pmdi)
                rgfOut &= _GetAttributesOf(pmdi, *prgfInOut);
        }
    }

    *prgfInOut = rgfOut;
    return S_OK;
}

HRESULT _CreateProgress(HWND hwnd, IProgressDialog **pppd)
{
    HRESULT hr = CoCreateInstance(CLSID_ProgressDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IProgressDialog, pppd));
    if (SUCCEEDED(hr))
    {
        (*pppd)->StartProgressDialog(hwnd, NULL, PROGDLG_AUTOTIME, NULL);
    }
    return hr;
}

HRESULT CMediaDeviceFolder::_DeleteItems(HWND hwnd, IDataObject *pdtobj)
{
    IProgressDialog *ppd;
    HRESULT hr = _CreateProgress(hwnd, &ppd);
    if (SUCCEEDED(hr))
    {
        ppd->SetTitle(L"Deleting Files...");

        STGMEDIUM medium;
        LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
        if (pida)
        {
            for (UINT i = 0; i < pida->cidl; i++)
            {
                LPCMEDIADEVITEM pmdi = _IsValid(HIDA_GetPIDLItem(pida, i));
                if (pmdi)
                {
                    TCHAR szName[MAX_PATH];

                    ppd->SetLine(2, _DisplayName(pmdi, szName, ARRAYSIZE(szName)), FALSE, NULL);
                    ppd->SetProgress64(i, pida->cidl);

                    IWMDMStorageControl *pstgCtrl;
                    hr = _StroageFromItem(pmdi, IID_PPV_ARG(IWMDMStorageControl, &pstgCtrl));
                    if (SUCCEEDED(hr))
                    {
                        hr = pstgCtrl->Delete(WMDM_MODE_BLOCK, NULL);
                        if (SUCCEEDED(hr))
                        {
                            _ChangeNotifyItem(SHCNE_DELETE, (LPCITEMIDLIST)pmdi);
                        }
                        pstgCtrl->Release();

                        ppd->SetProgress64(i + 1, pida->cidl);

                        if (ppd->HasUserCancelled())
                        {
                            hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
                            break;
                        }
                    }
                }
                // free space changed on device, notify
                // SHChangeNotify(SHCNE_UPDATEITEM, 0, (LPCTSTR)_pidl, NULL);
            }
            HIDA_ReleaseStgMedium(pida, &medium);
        }
        ppd->StopProgressDialog();
        ppd->Release();
    }
    return hr;
}

HRESULT CMediaDeviceFolder::CallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr = S_OK;

    switch (uMsg) 
    {
    case DFM_MERGECONTEXTMENU:
        break;

    case DFM_INVOKECOMMANDEX:
    {
        DFMICS *pdfmics = (DFMICS *)lParam;
        switch (wParam)
        {
        case DFM_CMD_DELETE:
            hr = _DeleteItems(hwnd, pdtobj);
            break;

        case DFM_CMD_PROPERTIES:
            break;

        default:
            // This is common menu items, use the default code.
            hr = S_FALSE;
            break;
        }
        break;
    }

    default:
        hr = E_NOTIMPL;
        break;
    }

    return hr;
}

HRESULT CALLBACK CMediaDeviceFolder::_ItemsMenuCB(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    IContextMenuCB *pcmcb;
    HRESULT hr = psf->QueryInterface(IID_PPV_ARG(IContextMenuCB, &pcmcb));
    if (SUCCEEDED(hr))
    {
        hr = pcmcb->CallBack(psf, hwnd, pdtobj, uMsg, wParam, lParam);
        pcmcb->Release();
    }
    return hr;
}

class CExtractConstIcon : public IExtractIconW
{
public:
    CExtractConstIcon(LPCTSTR psz, UINT uID);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IExtractIconW
    STDMETHODIMP GetIconLocation(UINT uFlags, LPWSTR pszIconFile, UINT cchMax, int* piIndex, UINT* pwFlags);
    STDMETHODIMP Extract(LPCWSTR pszFile, UINT nIconIndex, HICON* phiconLarge, HICON* phiconSmall, UINT nIconSize);

private:
    LONG _cRef;
    TCHAR _szPath[MAX_PATH];
    UINT _uID;
};

CExtractConstIcon::CExtractConstIcon(LPCTSTR psz, UINT uID) : _cRef(1), _uID(uID)
{
    StrCpyN(_szPath, psz, ARRAYSIZE(_szPath));
}

HRESULT CExtractConstIcon::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CExtractConstIcon, IExtractIconW),
        { 0 },
    };
    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) CExtractConstIcon::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CExtractConstIcon::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

STDMETHODIMP CExtractConstIcon::GetIconLocation(UINT uFlags, LPWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
{
    StrCpyN(pszIconFile, _szPath, cchMax);
    *piIndex = _uID;
    *pwFlags = GIL_PERINSTANCE;
    return S_OK;
}

STDMETHODIMP CExtractConstIcon::Extract(LPCWSTR pszFile, UINT nIconIndex, HICON* phiconLarge, HICON* phiconSmall, UINT nIconSize)
{
    return S_FALSE; // do the extract for me
}

HRESULT CMediaDeviceFolder::_CreateExtractIcon(LPCMEDIADEVITEM pmdi, REFIID riid, void **ppv)
{
    *ppv = NULL;
    HRESULT hr = E_FAIL;

    if (_IsRoot())
    {
        #define IDI_AUDIOPLAYER         299

        CExtractConstIcon *pei = new CExtractConstIcon(TEXT("shell32.dll"), -IDI_AUDIOPLAYER);
        if (pei)
        {
            hr = pei->QueryInterface(riid, ppv);
            pei->Release();
        }
    }
    else
    {
        WCHAR szName[MAX_PATH];
        hr = SHCreateFileExtractIconW(_Name(pmdi, szName, ARRAYSIZE(szName)), _IsFolder(pmdi), riid, ppv);
    }
    return hr;
}

HRESULT CMediaDeviceFolder::GetUIObjectOf(HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, 
                                  REFIID riid, UINT *prgfInOut, void **ppv)
{
    HRESULT hr = E_INVALIDARG;
    LPCMEDIADEVITEM pmdi = cidl ? _IsValid(apidl[0]) : NULL;

    if (pmdi &&
        (IsEqualIID(riid, IID_IExtractIconA) || IsEqualIID(riid, IID_IExtractIconW)))
    {
        hr = _CreateExtractIcon(pmdi, riid, ppv);
    }
    else if (IsEqualIID(riid, IID_IDataObject) && cidl)
    {
        hr = CIDLData_CreateFromIDArray(_pidl, cidl, apidl, (IDataObject**) ppv);
    }
    else if (IsEqualIID(riid, IID_IContextMenu) && pmdi)
    {
        // get the association for these files and lets attempt to 
        // build the context menu for the selection.   

        IQueryAssociations *pqa;
        hr = GetUIObjectOf(hwnd, 1, apidl, IID_PPV_ARG_NULL(IQueryAssociations, &pqa));
        if (SUCCEEDED(hr))
        {
            HKEY ahk[3];
            DWORD cKeys = SHGetAssocKeys(pqa, ahk, ARRAYSIZE(ahk));
            hr = CDefFolderMenu_Create2(_pidl, hwnd, cidl, apidl, 
                                        this, _ItemsMenuCB, cKeys, ahk, 
                                        (IContextMenu **)ppv);
            
            SHRegCloseKeys(ahk, cKeys);
            pqa->Release();
        }
    }
    else if (IsEqualIID(riid, IID_IQueryAssociations) && pmdi)
    {
        TCHAR szName[MAX_PATH];
        hr = SHAssocCreateForFile(_Name(pmdi, szName, ARRAYSIZE(szName)), _IsFolder(pmdi), NULL, riid, ppv);
    }
    else if (IsEqualIID(riid, IID_IDropTarget) && pmdi && _IsFolder(pmdi))
    {
        // If a directory is selected in the view, the drop is going to a folder,
        // so we need to bind to that folder and ask it to create a drop target

        IShellFolder *psf;
        hr = BindToObject(apidl[0], NULL, IID_PPV_ARG(IShellFolder, &psf));
        if (SUCCEEDED(hr))
        {
            hr = psf->CreateViewObject(hwnd, IID_IDropTarget, ppv);
            psf->Release();
        }
    }

    return hr;
}

BOOL CMediaDeviceFolder::_FileInfo(LPCMEDIADEVITEM pmdi, DWORD dwFlags, SHFILEINFO *psfi)
{
    TCHAR szName[MAX_PATH];
    ASSERT(!(dwFlags & SHGFI_SYSICONINDEX));

    ZeroMemory(psfi, sizeof(*psfi));

    return (BOOL)SHGetFileInfo(_Name(pmdi, szName, ARRAYSIZE(szName)), _IsFolder(pmdi), psfi, sizeof(*psfi), SHGFI_USEFILEATTRIBUTES | dwFlags);
}

LPCTSTR CMediaDeviceFolder::_RawName(LPCMEDIADEVITEM pmdi, LPTSTR psz, UINT cch)
{
    ualstrcpyn(psz, pmdi->szName, cch);
    return psz;
}

LPCTSTR CMediaDeviceFolder::_Name(LPCMEDIADEVITEM pmdi, LPTSTR psz, UINT cch)
{
    ualstrcpyn(psz, pmdi->szName, cch);
    return PathFindFileName(psz);       // some devices use path components in the names of the items
}

LPCTSTR CMediaDeviceFolder::_DisplayName(LPCMEDIADEVITEM pmdi, LPTSTR pszResult, UINT cch)
{
    SHFILEINFO sfi;
    if (_FileInfo(pmdi, SHGFI_DISPLAYNAME, &sfi))
    {
        StrCpyN(pszResult, sfi.szDisplayName, cch);
    }
    else
    {
        *pszResult = 0;
    }
    return pszResult;
}

HRESULT CMediaDeviceFolder::GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD dwFlags, STRRET *pStrRet)
{
    HRESULT hr;
    LPCMEDIADEVITEM pmdi = _IsValid(pidl);
    if (pmdi)
    {
        TCHAR szTemp[MAX_PATH];
        if (dwFlags & SHGDN_FORPARSING)
        {
            if (dwFlags & SHGDN_INFOLDER)
            {
                _RawName(pmdi, szTemp, ARRAYSIZE(szTemp));          // relative parse name
            }
            else
            {
                SHGetNameAndFlags(_pidl, dwFlags, szTemp, ARRAYSIZE(szTemp), NULL);

                TCHAR szName[MAX_PATH];
                PathAppend(szTemp, _RawName(pmdi, szName, ARRAYSIZE(szName)));
            }
        }
        else
        {
            _DisplayName(pmdi, szTemp, ARRAYSIZE(szTemp));
        }
        hr = StringToStrRetW(szTemp, pStrRet);
    }
    else
        hr = E_INVALIDARG;
    return hr;
}

HRESULT CMediaDeviceFolder::SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName, DWORD dwFlags, LPITEMIDLIST *ppidlOut)
{
    return E_NOTIMPL;
}

STDMETHODIMP CMediaDeviceFolder::GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv)
{
    HRESULT hr = E_FAIL;
    LPCMEDIADEVITEM pmdi = _IsValid(pidl);
    if (pmdi)
    {
        if (_IsRoot())
        {
            if (IsEqualSCID(*pscid, SCID_DESCRIPTIONID))
            {
                SHDESCRIPTIONID did = {0};
                did.dwDescriptionId = SHDID_COMPUTER_AUDIO;
                hr = InitVariantFromBuffer(pv, &did, sizeof(did));
            }
            else if (IsEqualSCID(*pscid, SCID_CAPACITY))
            {
                pv->vt = VT_UI8;
                pv->ullVal = pmdi->cbTotal.QuadPart;
                hr = S_OK;
            }
            else if (IsEqualSCID(*pscid, SCID_FREESPACE))
            {
                pv->vt = VT_UI8;
                pv->ullVal = pmdi->cbFree.QuadPart;
                hr = S_OK;
            }
            else if (IsEqualSCID(*pscid, SCID_DisplayProperties))
            {
                hr = InitVariantFromStr(pv, TEXT("prop:Name;Type;FreeSpace;Capacity"));
            }
        }
        else
        {
            if (IsEqualSCID(*pscid, SCID_TYPE))
            {
                TCHAR sz[64];
                hr = InitVariantFromStr(pv, _Type(pmdi, sz, ARRAYSIZE(sz)));
            }
            else if (IsEqualSCID(*pscid, SCID_SIZE))
            {
                pv->vt = VT_UI8;
                pv->ullVal = pmdi->cbTotal.QuadPart;
                hr = S_OK;
            }
        }
    }
    return hr;
}

LPCTSTR CMediaDeviceFolder::_Type(LPCMEDIADEVITEM pmdi, LPTSTR psz, UINT cch)
{
    *psz = 0;
    SHFILEINFO sfi;
    if (_FileInfo(pmdi, SHGFI_TYPENAME, &sfi))
        StrCpyN(psz, sfi.szTypeName, cch);
    return psz;
}

static const struct
{
    UINT iTitle;
    UINT cchCol;
    UINT iFmt;
    const SHCOLUMNID *pscid;
} 
c_aMediaDeviceColumns[] = 
{
    {IDS_NAME_COL,     40, LVCFMT_LEFT,  &SCID_NAME},
    {IDS_SIZE_COL,     10, LVCFMT_RIGHT, &SCID_SIZE},
    {IDS_TYPE_COL,     30, LVCFMT_LEFT,  &SCID_TYPE},
    {IDS_MODIFIED_COL, 20, LVCFMT_LEFT,  &SCID_WRITETIME},
};

HRESULT CMediaDeviceFolder::GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *pDetail)
{
    if (iColumn >= ARRAYSIZE(c_aMediaDeviceColumns))
        return E_NOTIMPL;
   
    HRESULT hr = S_OK;
    TCHAR szTemp[MAX_PATH];
    szTemp[0] = 0;

    if (NULL == pidl)
    {
        pDetail->fmt = c_aMediaDeviceColumns[iColumn].iFmt;
        pDetail->cxChar = c_aMediaDeviceColumns[iColumn].cchCol;
        LoadString(m_hInst, c_aMediaDeviceColumns[iColumn].iTitle, szTemp, ARRAYSIZE(szTemp));
    }
    else
    {
        LPCMEDIADEVITEM pmdi = _IsValid(pidl);
        if (pmdi)
        {
            // return the property to the caller that is being requested, this is based on the
            // list of columns we gave out when the view was created.
            switch (iColumn)
            {
            case DEV_COL_NAME:
                _DisplayName(pmdi, szTemp, ARRAYSIZE(szTemp));
                break;
    
            case DEV_COL_SIZE:
                if (!_IsFolder(pmdi))
                {
                    ULARGE_INTEGER ullSize = pmdi->cbTotal;
                    StrFormatKBSize(ullSize.QuadPart, szTemp, ARRAYSIZE(szTemp));
                }
                break;
    
            case DEV_COL_TYPE:
                _Type(pmdi, szTemp, ARRAYSIZE(szTemp));
                break;

            case DEV_COL_MODIFIED:
                SHFormatDateTime(&pmdi->ftModified, NULL, szTemp, ARRAYSIZE(szTemp));
                break;
            }             
        }
    }    
    return StringToStrRetW(szTemp, &(pDetail->str));
}

STDMETHODIMP CMediaDeviceFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
{
    HRESULT hr = E_INVALIDARG;
    ZeroMemory(pscid, sizeof(*pscid));

    if (!_IsRoot())
    {
        if (iColumn < ARRAYSIZE(c_aMediaDeviceColumns))
        {
            *pscid = *c_aMediaDeviceColumns[iColumn].pscid;
            hr = S_OK;
        }
    }
    return hr;
}

// IDelegateFolder
HRESULT CMediaDeviceFolder::SetItemAlloc(IMalloc *pmalloc)
{
    _fDelegate = TRUE;

    IUnknown_Set((IUnknown**)&_pmalloc, pmalloc);
    return S_OK;
}

CMediaDeviceEnum::CMediaDeviceEnum(CMediaDeviceFolder *pmdf, DWORD grfFlags) : _cRef(1), _grfFlags(grfFlags), _iIndex(0)
{
    _pmdf = pmdf;
    _pmdf->AddRef();

    // force full enum on this thread. avoid problem where calls made on the
    // marshaled thread fail due to authentication
    _Init();        

    DllAddRef();
}

int CALLBACK _FreeItems(ITEMIDLIST *pidl, IShellFolder *psf)
{
    ILFree(pidl);
    return 1;
}

CMediaDeviceEnum::~CMediaDeviceEnum()
{
    if ((HDPA)_dpaItems)
        _dpaItems.DestroyCallbackEx(_FreeItems, (IShellFolder *)NULL);

    _pmdf->Release();
    DllRelease();
}

HRESULT CMediaDeviceEnum::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CMediaDeviceEnum, IEnumIDList),                    // IID_IEnumIDList
        { 0 },
    };
    return QISearch(this, qit, riid, ppv);
}

STDMETHODIMP_(ULONG) CMediaDeviceEnum::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CMediaDeviceEnum::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

BOOL CMediaDeviceEnum::_FilterItem(BOOL bFolder)
{
    if (bFolder)
    {
        if (!(_grfFlags & SHCONTF_FOLDERS))
        {
            return TRUE;
        }
    }
    else if (!(_grfFlags & SHCONTF_NONFOLDERS))
    {
        return TRUE;
    }
    return FALSE;
}

BOOL CMediaDeviceFolder::_ShouldShowDevice(IWMDMDevice *pdev)
{
    BOOL bShow = TRUE;
    if (_fDelegate)
    {
        DWORD dwPowerSource, dwPercentRemaining;
        pdev->GetPowerSource(&dwPowerSource, &dwPercentRemaining);

        // heuristic to detect file system driver supported devices.
        // if it runs on batteries assume it is not a file system device

        bShow = (WMDM_POWER_CAP_BATTERY & dwPowerSource);
    }
    return bShow;
}


HRESULT CMediaDeviceEnum::_Init()
{
    if ((HDPA)_dpaItems)
        return S_OK;
    
    HRESULT hr = _dpaItems.Create(10) ? S_OK : E_OUTOFMEMORY;
    if (SUCCEEDED(hr))
    {
        if (_pmdf->_IsRoot())
        {
            IWMDMEnumDevice *pEnumDevice;
            hr = _pmdf->_EnumDevices(&pEnumDevice);
            if (SUCCEEDED(hr))
            {
                IWMDMDevice *pdev;
                ULONG ulFetched;
                while (S_OK == pEnumDevice->Next(1, &pdev, &ulFetched))
                {
                    if (_pmdf->_ShouldShowDevice(pdev))
                    {
                        ITEMIDLIST *pidl;
                        BOOL bFolder;
                        hr = _pmdf->_IDListForDevice(pdev, &pidl, &bFolder);
                        if (SUCCEEDED(hr))
                        {
                            if (_FilterItem(bFolder) ||
                                (-1 == _dpaItems.AppendPtr(pidl)))
                            {
                                ILFree(pidl);
                            }
                        }
                    }
                    pdev->Release();
                }
                pEnumDevice->Release();
            }
        }
        else
        {
            IWMDMEnumStorage *penum;
            hr = _pmdf->_EnumStorage(&penum);
            if (SUCCEEDED(hr))
            {
                ULONG ulFetched;
                IWMDMStorage *rgpstg[1];    // 1 is to WORKAROUND Rio600 faults on > 1
                while (SUCCEEDED(penum->Next(ARRAYSIZE(rgpstg), rgpstg, &ulFetched)))
                {
                    for (UINT i = 0; i < ulFetched; i++)
                    {
                        ITEMIDLIST *pidl;
                        BOOL bFolder;
                        hr = _pmdf->_IDListForStorage(rgpstg[i], &pidl, &bFolder);
                        if (SUCCEEDED(hr))
                        {
                            if (_FilterItem(bFolder) ||
                                (-1 == _dpaItems.AppendPtr(pidl)))
                            {
                                ILFree(pidl);
                            }
                        }
                        rgpstg[i]->Release();
                    }

                    if (ulFetched != ARRAYSIZE(rgpstg))
                        break;
                }
                penum->Release();
            }
        }
    }
    return hr;
}

HRESULT CMediaDeviceEnum::Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched)
{
    HRESULT hr = _Init();
    if (SUCCEEDED(hr))
    {
        hr = S_FALSE;
        if (_iIndex < _dpaItems.GetPtrCount())
        {
            hr = SHILClone(_dpaItems.GetPtr(_iIndex++), rgelt);
        }
        if (pceltFetched)
            *pceltFetched = (hr == S_OK) ? 1 : 0;
    }
    return hr;
}

CMediaDeviceDropTarget::CMediaDeviceDropTarget(CMediaDeviceFolder *pmdf, HWND hwnd) :
    _cRef(1), _pmdf(pmdf), _hwnd(hwnd), _grfKeyStateLast(-1)
{
    _pmdf->AddRef();
    // use the FTM to make the call back interface calls unmarshaled
    CoCreateFreeThreadedMarshaler(SAFECAST(this, IWMDMProgress2 *), &_punkFTM);
    DllAddRef();
}

CMediaDeviceDropTarget::~CMediaDeviceDropTarget()
{
    DragLeave();
    _pmdf->Release();
    if (_punkFTM)
        _punkFTM->Release();
    DllRelease();
}

STDMETHODIMP_(ULONG) CMediaDeviceDropTarget::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CMediaDeviceDropTarget::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

HRESULT CMediaDeviceDropTarget::QueryInterface(REFIID riid, void** ppv)
{
    static const QITAB qit[] = {
        QITABENT(CMediaDeviceDropTarget, IDropTarget),
        QITABENTMULTI(CMediaDeviceDropTarget, IWMDMProgress, IWMDMProgress2),
        QITABENT     (CMediaDeviceDropTarget, IWMDMProgress2),
        QITABENT(CMediaDeviceDropTarget, INamespaceWalkCB),
        { 0 },
    };
    HRESULT hr = QISearch(this, qit, riid, ppv);
    if (FAILED(hr) && (IID_IMarshal == riid) && _punkFTM)
        hr = _punkFTM->QueryInterface(riid, ppv);
    return hr;
}


CLIPFORMAT g_cfPreferredDropEffect = 0;         // IMPLEMENT
CLIPFORMAT g_cfLogicalPerformedDropEffect = 0;
CLIPFORMAT g_cfPerformedDropEffect = 0;

#define POPUP_NONDEFAULTDD 1
#define DDIDM_COPY 2
#define DDIDM_MOVE 3
#define DDIDM_LINK 4

DWORD CMediaDeviceDropTarget::_GetDropEffect(DWORD *pdwEffect, DWORD grfKeyState)
{
    DWORD dwEffectReturned = DROPEFFECT_NONE;
    switch (grfKeyState & (MK_CONTROL | MK_SHIFT | MK_ALT))
    {
    case MK_CONTROL:            dwEffectReturned = DROPEFFECT_COPY; break;
    case MK_SHIFT:              dwEffectReturned = DROPEFFECT_MOVE; break;
    case MK_SHIFT | MK_CONTROL: dwEffectReturned = DROPEFFECT_LINK; break;
    case MK_ALT:                dwEffectReturned = DROPEFFECT_LINK; break;
    
    default:
        {
            // no modifier keys:
            // if the data object contains a preferred drop effect, try to use it
            DWORD dwPreferred = DataObj_GetDWORD(_pdtobj, g_cfPreferredDropEffect, DROPEFFECT_NONE) & *pdwEffect;
            if (dwPreferred)
            {
                if (dwPreferred & DROPEFFECT_MOVE)
                {
                    dwEffectReturned = DROPEFFECT_MOVE;
                }
                else if (dwPreferred & DROPEFFECT_COPY)
                {
                    dwEffectReturned = DROPEFFECT_COPY;
                }
                else if (dwPreferred & DROPEFFECT_LINK)
                {
                    dwEffectReturned = DROPEFFECT_LINK;
                }
            }
            else
            {
                dwEffectReturned = DROPEFFECT_COPY;
            }
        }
        break;
    }
    return dwEffectReturned;
}

STDMETHODIMP CMediaDeviceDropTarget::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
    IUnknown_Set((IUnknown **)&_pdtobj, pdtobj);

    _grfKeyStateLast = grfKeyState;

    if (pdwEffect)
        *pdwEffect = _dwEffectLastReturned = _GetDropEffect(pdwEffect, grfKeyState);

    return S_OK;
}

STDMETHODIMP CMediaDeviceDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
    // has the key state changed?  if not then lets return the previously cached 
    // version, otherwise recompute.

    if (_grfKeyStateLast == grfKeyState)
    {
        if (*pdwEffect)
            *pdwEffect = _dwEffectLastReturned;
    }
    else if (*pdwEffect)
    {
        *pdwEffect = _GetDropEffect(pdwEffect, grfKeyState);
    }

    _dwEffectLastReturned = *pdwEffect;
    _grfKeyStateLast = grfKeyState;

    return S_OK;
}
 
STDMETHODIMP CMediaDeviceDropTarget::DragLeave()
{
    ATOMICRELEASE(_pdtobj);
    return S_OK;
}

class COPY_THREAD_DATA
{
public:
    COPY_THREAD_DATA(CMediaDeviceDropTarget *pmddt) : _pmddt(pmddt), _pstm(NULL)
    {
        _pmddt->AddRef();
    }

    ~COPY_THREAD_DATA() 
    {
        _pmddt->Release();
        if (_pstm)
            _pstm->Release();
    }

    HRESULT Marshal(IDataObject *pdtobj)
    {
        return CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pdtobj, &_pstm);
    }

    HRESULT UnMarshal(IDataObject **ppdtobj)
    {
        HRESULT hr = CoGetInterfaceAndReleaseStream(_pstm, IID_PPV_ARG(IDataObject, ppdtobj));
        _pstm = NULL;
        return hr;
    }

    CMediaDeviceDropTarget *_pmddt;

private:
    IStream *_pstm;                  // IStream for marshaling the IDataObject
};

STDMETHODIMP CMediaDeviceDropTarget::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
{
    *pdwEffect = DROPEFFECT_NONE;           // incase of failure

    // determine the type of operation to performed, if the right button is down
    // then lets display the menu, otherwise base it on the drop effect

    UINT idCmd = 0;                             // Choice from drop popup menu

#if 0
    if (!(_grfKeyStateLast & MK_LBUTTON))
    {
        HMENU hMenu = SHLoadPopupMenu(m_hInst, POPUP_NONDEFAULTDD);
        if (!hMenu)
        {
            DragLeave();
            return E_FAIL;
        }
        
        SetMenuDefaultItem(hMenu, POPUP_NONDEFAULTDD, FALSE);
        idCmd = TrackPopupMenu(hMenu, 
                               TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTALIGN,
                               pt.x, pt.y, 0, _hwnd, NULL);
        DestroyMenu(hMenu);
    }
    else
#endif
    {
        switch (_GetDropEffect(pdwEffect, grfKeyState))
        {
        case DROPEFFECT_COPY:   idCmd = DDIDM_COPY; break;
        case DROPEFFECT_MOVE:   idCmd = DDIDM_MOVE; break;
        }
    }

    // now perform the operation, based on the command ID we have.

    HRESULT hr = E_FAIL;
    switch (idCmd)
    {
    case DDIDM_COPY:
        COPY_THREAD_DATA *pctd = new COPY_THREAD_DATA(this);
        if (pctd)
        {
            if (SUCCEEDED(pctd->Marshal(pdtobj)))
            {
                if (!SHCreateThread(_DoCopyThreadProc, pctd, CTF_COINIT, NULL))
                {
                    delete pctd;
                }
            }
        }
        break;
    }

#if 0
    // success so lets populate the new changes to the effect

    if (SUCCEEDED(hr) && *pdwEffect)
    {
        DataObj_SetDWORD(pdtobj, g_cfLogicalPerformedDropEffect, *pdwEffect);
        DataObj_SetDWORD(pdtobj, g_cfPerformedDropEffect, *pdwEffect);
    }
#endif

    DragLeave();
    return hr;
}

DWORD CMediaDeviceDropTarget::_DoCopyThreadProc(void *pv)
{
    COPY_THREAD_DATA *pctd = (COPY_THREAD_DATA *)pv;
    IDataObject *pdtobj;
    HRESULT hr = pctd->UnMarshal(&pdtobj);
    if (SUCCEEDED(hr))
    {
        pctd->_pmddt->_DoCopy(pdtobj);
        pdtobj->Release();
    }
    delete pctd;
    return 0;
}

HRESULT CMediaDeviceDropTarget::_DoCopy(IDataObject *pdtobj)
{
    IProgressDialog *ppd;
    HRESULT hr = _CreateProgress(_hwnd, &ppd);
    if (SUCCEEDED(hr))
    {
        ppd->SetTitle(L"Copying Files...");

        IWMDMStorageControl *pstgCtrl;
        hr = _pmdf->_Storage(IID_PPV_ARG(IWMDMStorageControl, &pstgCtrl));
        if (SUCCEEDED(hr))
        {
            INamespaceWalk *pnsw;
            hr = CoCreateInstance(CLSID_NamespaceWalker, NULL, CLSCTX_INPROC, IID_PPV_ARG(INamespaceWalk, &pnsw));
            if (SUCCEEDED(hr))
            {
                hr = pnsw->Walk(pdtobj, 0, 4, SAFECAST(this, INamespaceWalkCB *));
                if (SUCCEEDED(hr))
                {
                    UINT cItems;
                    LPITEMIDLIST *ppidls;
                    hr = pnsw->GetIDArrayResult(&cItems, &ppidls);
                    if (SUCCEEDED(hr))
                    {
                        for (UINT i = 0; SUCCEEDED(hr) && (i < cItems); i++)
                        {
                            TCHAR szPath[MAX_PATH];
                            if (SHGetPathFromIDList(ppidls[i], szPath))
                            {
                                ppd->SetLine(2, PathFindFileName(szPath), FALSE, NULL);
                                ppd->SetProgress64(_ulProgressCurrent, _ulProgressTotal);

                                _ppd = ppd; // used by the callback interface

                                IWMDMStorage *pstgNew;
                                hr = pstgCtrl->Insert(WMDM_MODE_BLOCK | WMDM_CONTENT_FILE,
                                        szPath, NULL, SAFECAST(this, IWMDMProgress2 *), &pstgNew);
                                if (SUCCEEDED(hr))
                                {
                                    _pmdf->_ChangeNotifyObj(SHCNE_CREATE, pstgNew);
                                    pstgNew->Release();

                                    ppd->SetProgress64(_ulProgressCurrent, _ulProgressTotal);

                                    if (ppd->HasUserCancelled())
                                    {
                                        hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
                                    }
                                }
                            }
                        }
                        FreeIDListArray(ppidls, cItems);

                        // free space changed on device, notify
                        // SHChangeNotify(SHCNE_UPDATEITEM, 0, (LPCTSTR)_pmdf->GetIDList(), NULL);
                    }
                }
                pnsw->Release();
            }
            pstgCtrl->Release();
        }
        ppd->StopProgressDialog();
        ppd->Release();
    }

    return S_OK;
}

// IWMDMProgress
// since we use the FTM we get called here on an MTA thread
STDMETHODIMP CMediaDeviceDropTarget::Begin(DWORD dwEstimatedTicks)
{
    _ulThisFile = dwEstimatedTicks;
    return S_OK;    // dwEstimatedTicks size in bytes of file
}

STDMETHODIMP CMediaDeviceDropTarget::Progress(DWORD dwTranspiredTicks)
{
    _ppd->SetProgress64(_ulProgressCurrent + dwTranspiredTicks, _ulProgressTotal);

    if (_ulThisFile == dwTranspiredTicks)
        _ulProgressCurrent += _ulThisFile;  // end of this file, advance total

    return _ppd->HasUserCancelled() ? HRESULT_FROM_WIN32(ERROR_CANCELLED) : S_OK;
}

STDMETHODIMP CMediaDeviceDropTarget::End()
{
    return S_OK;
}

// IWMDMProgress2
STDMETHODIMP CMediaDeviceDropTarget::End2(HRESULT hrCompletionCode)
{
    return S_OK;
}

// INamespaceWalkCB
STDMETHODIMP CMediaDeviceDropTarget::FoundItem(IShellFolder *psf, LPCITEMIDLIST pidl)
{
    IShellFolder2 *psf2;
    if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2))))
    {
        ULONGLONG ul;
        if (SUCCEEDED(GetLongProperty(psf2, pidl, &SCID_SIZE, &ul)))
        {
            // TCHAR szName[MAX_PATH];
            // DisplayNameOf(psf, pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName));
            _ulProgressTotal += ul;
        }
        psf2->Release();
    }
    return S_OK;
}

STDMETHODIMP CMediaDeviceDropTarget::EnterFolder(IShellFolder *psf, LPCITEMIDLIST pidl)
{
    return S_OK;
}

STDMETHODIMP CMediaDeviceDropTarget::LeaveFolder(IShellFolder *psf, LPCITEMIDLIST pidl)
{
    return S_OK;
}