WindowsXP-SP1/shell/shdocvw/mruex.cpp

1317 lines
33 KiB
C++

#include "priv.h"
#pragma hdrstop
// this is swiped from comctl32\mru.c
#define SLOT_LOADED 0x01
#define SLOT_USED 0x02
typedef struct _SLOTITEMDATA
{
DWORD state;
DWORD cb;
BYTE *p;
} SLOTITEMDATA;
class CMruBase : public IMruDataList
{
public:
CMruBase() : _cRef(1) {}
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppvOut);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
// IMruDataList (maybe?)
STDMETHODIMP InitData(
UINT uMax,
MRULISTF flags,
HKEY hKey,
LPCWSTR pszSubKey,
MRUDATALISTCOMPARE pfnCompare);
STDMETHODIMP AddData(const BYTE *pData, DWORD cbData, DWORD *pdwSlot);
STDMETHODIMP FindData(const BYTE *pData, DWORD cbData, int *piIndex);
STDMETHODIMP GetData(int iIndex, BYTE *pData, DWORD cbData);
STDMETHODIMP QueryInfo(int iIndex, DWORD *pdwSlot, DWORD *pcbData);
STDMETHODIMP Delete(int iItem);
protected:
virtual ~CMruBase();
HRESULT _GetItem(int iIndex, SLOTITEMDATA **ppitem);
HRESULT _GetSlotItem(DWORD dwSlot, SLOTITEMDATA **ppitem);
HRESULT _LoadItem(DWORD dwSlot);
HRESULT _AddItem(DWORD dwSlot, const BYTE *pData, DWORD cbData);
void _DeleteItem(DWORD dwSlot);
HRESULT _UseEmptySlot(DWORD *pdwSlot);
void _CheckUsedSlots();
// virtuals that are optionally implemented
virtual BOOL _IsEqual(SLOTITEMDATA *pitem, const BYTE *pData, DWORD cbData);
virtual void _DeleteValue(LPCWSTR psz);
// virtuals that must be implemented
virtual HRESULT _InitSlots() = 0;
virtual void _SaveSlots() = 0;
virtual DWORD _UpdateSlots(int iIndex) = 0;
virtual void _SlotString(DWORD dwSlot, LPWSTR psz, DWORD cch) = 0;
virtual HRESULT _GetSlot(int iIndex, DWORD *pdwSlot) = 0;
virtual HRESULT _RemoveSlot(int iIndex, DWORD *pdwSlot) = 0;
protected:
LONG _cRef;
MRULISTF _flags;
BOOL _fDirty;
BOOL _fSlotsChecked;
HKEY _hkMru;
int _cMaxSlots;
int _cUsedSlots;
MRUDATALISTCOMPARE _pfnCompare;
SLOTITEMDATA *_pItems;
};
class CMruLongList : public CMruBase
{
protected:
virtual ~CMruLongList() { if (_rgdwSlots) { LocalFree(_rgdwSlots); _rgdwSlots = NULL; } }
void _ImportShortList();
virtual HRESULT _InitSlots();
virtual void _SaveSlots();
virtual DWORD _UpdateSlots(int iIndex);
virtual void _SlotString(DWORD dwSlot, LPWSTR psz, DWORD cch);
virtual HRESULT _GetSlot(int iIndex, DWORD *pdwSlot);
virtual HRESULT _RemoveSlot(int iIndex, DWORD *pdwSlot);
private:
DWORD *_rgdwSlots;
};
STDMETHODIMP_(ULONG) CMruBase::AddRef()
{
return InterlockedIncrement(&_cRef);
}
#define szMRUEX TEXT("MRUListEx")
#define szMRUEX_OLD TEXT("MRUList")
STDMETHODIMP_(ULONG) CMruBase::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
_SaveSlots();
delete this;
return 0;
}
STDMETHODIMP CMruBase::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] = {
QITABENT(CMruBase, IMruDataList), // IID_IMruDataList
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
CMruBase::~CMruBase()
{
if (_hkMru)
RegCloseKey(_hkMru);
if (_pItems)
{
for (int i = 0; i < _cUsedSlots; i++)
{
if (_pItems[i].p)
{
LocalFree(_pItems[i].p);
_pItems[i].p = NULL;
}
}
LocalFree(_pItems);
_pItems = NULL;
}
}
class CMruShortList : public CMruBase
{
protected:
virtual ~CMruShortList() { if (_rgchSlots) { LocalFree(_rgchSlots); _rgchSlots = NULL; } }
virtual HRESULT _InitSlots();
virtual void _SaveSlots();
virtual DWORD _UpdateSlots(int iIndex);
virtual void _SlotString(DWORD dwSlot, LPWSTR psz, DWORD cch);
virtual HRESULT _GetSlot(int iIndex, DWORD *pdwSlot);
virtual HRESULT _RemoveSlot(int iIndex, DWORD *pdwSlot);
friend class CMruLongList;
private:
WCHAR *_rgchSlots;
};
HRESULT CMruShortList::_InitSlots()
{
HRESULT hr = E_OUTOFMEMORY;
DWORD cb = (_cMaxSlots + 1) * sizeof(_rgchSlots[0]);
_rgchSlots = (WCHAR *) LocalAlloc(LPTR, cb);
if (_rgchSlots)
{
// Do we already have the new MRU Index?
// Then validate it. You can never trust the registry not to be corrupted.
// Must be at least the size of a DWORD
// Must be a multiple of DWORD in length
// Must end in a -1
if (NOERROR == SHGetValue(_hkMru, NULL, szMRUEX_OLD, NULL, (LPBYTE)_rgchSlots, &cb))
{
ASSERT(!(cb % 2));
_cUsedSlots = (cb / sizeof(_rgchSlots[0])) - 1;
ASSERT(_rgchSlots[_cUsedSlots] == 0);
}
_rgchSlots[_cUsedSlots] = 0;
hr = S_OK;
}
return hr;
}
void CMruShortList::_SaveSlots()
{
if (_fDirty)
{
SHSetValue(_hkMru, NULL, szMRUEX_OLD, REG_SZ, (BYTE *)_rgchSlots, sizeof(_rgchSlots[0]) * (_cUsedSlots + 1));
_fDirty = FALSE;
}
}
#define BASE_CHAR TEXT('a')
void CMruShortList::_SlotString(DWORD dwSlot, LPWSTR psz, DWORD cch)
{
if (cch > 1)
{
psz[0] = (WCHAR) dwSlot + BASE_CHAR;
psz[1] = 0;
}
}
HRESULT CMruShortList::_GetSlot(int iIndex, DWORD *pdwSlot)
{
HRESULT hr = E_FAIL;
if (iIndex < _cUsedSlots)
{
// its in our range of allocated slots
if (_rgchSlots[iIndex] - BASE_CHAR < _cMaxSlots)
{
*pdwSlot = _rgchSlots[iIndex] - BASE_CHAR;
_pItems[*pdwSlot].state |= SLOT_USED;
hr = S_OK;
}
}
return hr;
}
HRESULT CMruShortList::_RemoveSlot(int iIndex, DWORD *pdwSlot)
{
HRESULT hr = _GetSlot(iIndex, pdwSlot);
if (SUCCEEDED(hr))
{
// MoveMemory() handles overlapping ranges
MoveMemory(&_rgchSlots[iIndex], &_rgchSlots[iIndex+1], (_cUsedSlots - iIndex) * sizeof(_rgchSlots[0]));
_cUsedSlots--;
// unuse the slot
_pItems->state &= ~SLOT_USED;
_fDirty = TRUE;
}
return hr;
}
DWORD CMruShortList::_UpdateSlots(int iIndex)
{
// need to move this away
DWORD dwSlot;
DWORD cb = iIndex * sizeof(_rgchSlots[0]);
if (iIndex != _cUsedSlots)
dwSlot = _rgchSlots[iIndex] - BASE_CHAR;
else
{
// we are at the end of the list
// see if we can grow
// find the first unused slot
if (SUCCEEDED(_UseEmptySlot(&dwSlot)))
{
// need to move the terminator
cb += sizeof(_rgchSlots[0]);
}
else
{
// dont move the the terminator
// and dont move the last slot
dwSlot = _rgchSlots[_cUsedSlots - 1] - BASE_CHAR;
cb -= sizeof(_rgchSlots[0]);
}
}
if (cb)
{
// MoveMemory() handles overlapping ranges
MoveMemory(&_rgchSlots[1], &_rgchSlots[0], cb);
_rgchSlots[0] = (WCHAR) dwSlot + BASE_CHAR;
_fDirty = TRUE;
}
return dwSlot;
}
HRESULT CMruBase::InitData(
UINT uMax,
MRULISTF flags,
HKEY hKey,
LPCWSTR pszSubKey,
MRUDATALISTCOMPARE pfnCompare)
{
HRESULT hr = E_FAIL;
_flags = flags;
_pfnCompare = pfnCompare;
_cMaxSlots = uMax;
if (pszSubKey)
{
RegCreateKeyEx(hKey, pszSubKey, 0L, NULL, 0, MAXIMUM_ALLOWED, NULL, &_hkMru, NULL);
}
else
_hkMru = SHRegDuplicateHKey(hKey);
if (_hkMru)
{
_pItems = (SLOTITEMDATA *) LocalAlloc(LPTR, sizeof(SLOTITEMDATA) * _cMaxSlots);
if (_pItems)
hr = _InitSlots();
else
hr = E_OUTOFMEMORY;
}
return hr;
}
void CMruBase::_CheckUsedSlots()
{
ASSERT(!_fSlotsChecked);
DWORD dwSlot;
for (int i = 0; i < _cUsedSlots; i++)
{
_GetSlot(i, &dwSlot);
}
_fSlotsChecked = TRUE;
}
HRESULT CMruBase::_AddItem(DWORD dwSlot, const BYTE *pData, DWORD cbData)
{
SLOTITEMDATA *pitem = &_pItems[dwSlot];
WCHAR szSlot[12];
_SlotString(dwSlot, szSlot, ARRAYSIZE(szSlot));
HRESULT hr = E_OUTOFMEMORY;
if (NOERROR == SHSetValue(_hkMru, NULL, szSlot, REG_BINARY, pData, cbData))
{
if (cbData >= pitem->cb || !pitem->p)
{
if (pitem->p)
LocalFree(pitem->p);
// Binary data has the size at the begining so we'll need a little extra room.
pitem->p = (BYTE *)LocalAlloc(LPTR, cbData);
}
if (pitem->p)
{
pitem->cb = cbData;
pitem->state = (SLOT_LOADED | SLOT_USED);
memcpy(pitem->p, pData, cbData);
hr = S_OK;
}
}
return hr;
}
HRESULT CMruBase::AddData(const BYTE *pData, DWORD cbData, DWORD *pdwSlot)
{
HRESULT hr = E_FAIL;
int iIndex;
DWORD dwSlot;
if (SUCCEEDED(FindData(pData, cbData, &iIndex)))
{
dwSlot = _UpdateSlots(iIndex);
hr = S_OK;
}
else
{
dwSlot = _UpdateSlots(_cUsedSlots);
hr = _AddItem(dwSlot, pData, cbData);
}
if (SUCCEEDED(hr) && pdwSlot)
*pdwSlot = dwSlot;
return hr;
}
BOOL CMruBase::_IsEqual(SLOTITEMDATA *pitem, const BYTE *pData, DWORD cbData)
{
BOOL fRet = FALSE;
if (_pfnCompare)
{
fRet = (0 == _pfnCompare(pData, pitem->p, cbData));
}
else
{
switch (_flags & 0xf)
{
case MRULISTF_USE_MEMCMP:
if (pitem->cb == cbData)
fRet = (0 == memcmp(pData, pitem->p, min(cbData, pitem->cb)));
break;
case MRULISTF_USE_STRCMPIW:
fRet = (0 == StrCmpIW((LPCWSTR)pData, (LPCWSTR)pitem->p));
break;
case MRULISTF_USE_STRCMPW:
fRet = (0 == StrCmpW((LPCWSTR)pData, (LPCWSTR)pitem->p));
break;
case MRULISTF_USE_ILISEQUAL:
fRet = ILIsEqual((LPCITEMIDLIST)pData, (LPCITEMIDLIST)pitem->p);
break;
}
}
return fRet;
}
HRESULT CMruBase::FindData(const BYTE *pData, DWORD cbData, int *piIndex)
{
HRESULT hr = E_FAIL;
for (int iIndex = 0; iIndex < _cUsedSlots ; iIndex++)
{
SLOTITEMDATA *pitem;
if (SUCCEEDED(_GetItem(iIndex, &pitem)))
{
if (_IsEqual(pitem, pData, cbData))
{
hr = S_OK;
*piIndex = iIndex;
break;
}
}
}
return hr;
}
HRESULT CMruBase::_LoadItem(DWORD dwSlot)
{
SLOTITEMDATA *pitem = &_pItems[dwSlot];
DWORD cb;
WCHAR szSlot[12];
_SlotString(dwSlot, szSlot, ARRAYSIZE(szSlot));
ASSERT(!(pitem->state & SLOT_LOADED));
ASSERT(pitem->state & SLOT_USED);
if (NOERROR == SHGetValue(_hkMru, NULL, szSlot, NULL, NULL, &cb) && cb)
{
// Binary data has the size at the begining so we'll need a little extra room.
pitem->p = (BYTE *)LocalAlloc(LPTR, cb);
if (pitem->p)
{
pitem->cb = cb;
if (NOERROR != SHGetValue(_hkMru, NULL, szSlot, NULL, pitem->p, &cb))
{
LocalFree(pitem->p);
pitem->p = NULL;
}
}
}
pitem->state |= SLOT_LOADED;
return pitem->p ? S_OK : E_FAIL;
}
HRESULT CMruBase::_GetSlotItem(DWORD dwSlot, SLOTITEMDATA **ppitem)
{
HRESULT hr = S_OK;
ASSERT(dwSlot < (DWORD)_cMaxSlots);
if (!(_pItems[dwSlot].state & SLOT_LOADED))
_LoadItem(dwSlot);
if (_pItems[dwSlot].p)
{
*ppitem = &_pItems[dwSlot];
return S_OK;
}
return E_OUTOFMEMORY;
}
HRESULT CMruBase::_GetItem(int iIndex, SLOTITEMDATA **ppitem)
{
DWORD dwSlot;
HRESULT hr = _GetSlot(iIndex, &dwSlot);
if (SUCCEEDED(hr))
{
hr = _GetSlotItem(dwSlot, ppitem);
}
return hr;
}
HRESULT CMruBase::GetData(int iIndex, BYTE *pData, DWORD cbData)
{
SLOTITEMDATA *pitem;
HRESULT hr = _GetItem(iIndex, &pitem);
if (SUCCEEDED(hr))
{
if (pitem->cb <= cbData)
{
memcpy(pData, pitem->p, min(cbData, pitem->cb));
}
else
hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
}
return hr;
}
HRESULT CMruBase::QueryInfo(int iIndex, DWORD *pdwSlot, DWORD *pcbData)
{
DWORD dwSlot;
HRESULT hr = _GetSlot(iIndex, &dwSlot);
if (SUCCEEDED(hr))
{
if (pdwSlot)
*pdwSlot = dwSlot;
if (pcbData)
{
SLOTITEMDATA *pitem;
hr = _GetSlotItem(dwSlot, &pitem);
if (SUCCEEDED(hr))
{
*pcbData = pitem->cb;
}
}
}
return hr;
}
HRESULT CMruBase::_UseEmptySlot(DWORD *pdwSlot)
{
HRESULT hr = E_FAIL;
if (!_fSlotsChecked)
_CheckUsedSlots();
for (DWORD dw = 0; dw < (DWORD) _cMaxSlots; dw++)
{
if (!(_pItems[dw].state & SLOT_USED))
{
_pItems[dw].state |= SLOT_USED;
*pdwSlot = dw;
_cUsedSlots++;
hr = S_OK;
break;
}
}
return hr;
}
void CMruBase::_DeleteValue(LPCWSTR psz)
{
SHDeleteValue(_hkMru, NULL, psz);
}
void CMruBase::_DeleteItem(DWORD dwSlot)
{
ASSERT(dwSlot < (DWORD) _cMaxSlots);
WCHAR szSlot[12];
_SlotString(dwSlot, szSlot, ARRAYSIZE(szSlot));
_DeleteValue(szSlot);
if (_pItems[dwSlot].p)
{
LocalFree(_pItems[dwSlot].p);
_pItems[dwSlot].p = NULL;
}
}
HRESULT CMruBase::Delete(int iIndex)
{
DWORD dwSlot;
HRESULT hr = _RemoveSlot(iIndex, &dwSlot);
if (SUCCEEDED(hr))
{
_DeleteItem(dwSlot);
}
return hr;
}
void CMruLongList::_ImportShortList()
{
CMruShortList *pmru = new CMruShortList();
if (pmru)
{
if (SUCCEEDED(pmru->InitData(_cMaxSlots, 0, _hkMru, NULL, NULL)))
{
// we need to walk the list
DWORD dwSlot;
SLOTITEMDATA *pitem;
while (SUCCEEDED(pmru->_GetSlot(_cUsedSlots, &dwSlot))
&& SUCCEEDED(pmru->_GetSlotItem(dwSlot, &pitem)))
{
// we just copy to ourselves
_AddItem(dwSlot, pitem->p, pitem->cb);
pmru->_DeleteItem(dwSlot);
// dont use _UpdateSlots() here
_rgdwSlots[_cUsedSlots] = dwSlot;
_cUsedSlots++;
}
_fDirty = TRUE;
}
pmru->Release();
// wipe it out
SHDeleteValue(_hkMru, NULL, szMRUEX_OLD);
}
}
HRESULT CMruLongList::_InitSlots()
{
HRESULT hr = E_OUTOFMEMORY;
DWORD cb = (_cMaxSlots + 1) * sizeof(_rgdwSlots[0]);
_rgdwSlots = (DWORD *) LocalAlloc(LPTR, cb);
if (_rgdwSlots)
{
// Do we already have the new MRU Index?
// Then validate it. You can never trust the registry not to be corrupted.
// Must be at least the size of a DWORD
// Must be a multiple of DWORD in length
// Must end in a -1
if (NOERROR == SHGetValue(_hkMru, NULL, szMRUEX, NULL, (LPBYTE)_rgdwSlots, &cb))
{
ASSERT(!(cb % 4));
_cUsedSlots = (cb / sizeof(_rgdwSlots[0])) - 1;
ASSERT(_rgdwSlots[_cUsedSlots] == -1);
}
else
{
_ImportShortList();
}
_rgdwSlots[_cUsedSlots] = (DWORD)-1;
hr = S_OK;
}
return hr;
}
void CMruLongList::_SaveSlots()
{
if (_fDirty)
{
SHSetValue(_hkMru, NULL, szMRUEX, REG_BINARY, (BYTE *)_rgdwSlots, sizeof(_rgdwSlots[0]) * (_cUsedSlots + 1));
_fDirty = FALSE;
}
}
void CMruLongList::_SlotString(DWORD dwSlot, LPWSTR psz, DWORD cch)
{
wnsprintfW(psz, cch, L"%d", dwSlot);
}
HRESULT CMruLongList::_GetSlot(int iIndex, DWORD *pdwSlot)
{
HRESULT hr = E_FAIL;
ASSERT(iIndex < _cMaxSlots);
if (iIndex < _cUsedSlots)
{
// its in our range of allocated slots
if (_rgdwSlots[iIndex] < (DWORD) _cMaxSlots)
{
*pdwSlot = _rgdwSlots[iIndex];
_pItems[*pdwSlot].state |= SLOT_USED;
hr = S_OK;
}
}
return hr;
}
HRESULT CMruLongList::_RemoveSlot(int iIndex, DWORD *pdwSlot)
{
HRESULT hr = _GetSlot(iIndex, pdwSlot);
if (SUCCEEDED(hr))
{
// MoveMemory() handles overlapping ranges
MoveMemory(&_rgdwSlots[iIndex], &_rgdwSlots[iIndex+1], (_cUsedSlots - iIndex) * sizeof(_rgdwSlots[0]));
_cUsedSlots--;
// unuse the slot
_pItems->state &= ~SLOT_USED;
_fDirty = TRUE;
}
return hr;
}
DWORD CMruLongList::_UpdateSlots(int iIndex)
{
// need to move this away
DWORD dwSlot;
DWORD cb = iIndex * sizeof(_rgdwSlots[0]);
if (iIndex != _cUsedSlots)
dwSlot = _rgdwSlots[iIndex];
else
{
// we are at the end of the list
// see if we can grow
// find the first unused slot
if (SUCCEEDED(_UseEmptySlot(&dwSlot)))
{
// need to move the terminator
cb += sizeof(_rgdwSlots[0]);
}
else
{
// dont move the the terminator
// and dont move the last slot
dwSlot = _rgdwSlots[_cUsedSlots - 1];
cb -= sizeof(_rgdwSlots[0]);
}
}
if (cb)
{
// MoveMemory() handles overlapping ranges
MoveMemory(&_rgdwSlots[1], &_rgdwSlots[0], cb);
_rgdwSlots[0] = dwSlot;
_fDirty = TRUE;
}
return dwSlot;
}
STDAPI CMruLongList_CreateInstance(IUnknown * punkOuter, IUnknown ** ppunk, LPCOBJECTINFO poi)
{
*ppunk = NULL;
CMruLongList *p = new CMruLongList();
if (p != NULL)
{
*ppunk = SAFECAST(p, IMruDataList *);
return S_OK;
}
return E_OUTOFMEMORY;
}
class CMruPidlList;
class CMruNode : public CMruLongList
{
public:
CMruNode(CMruNode *pnodeParent, DWORD dwSlot);
HRESULT GetNode(BOOL fCreate, LPCITEMIDLIST pidlChild, CMruNode **ppnode);
HRESULT RemoveLeast(DWORD *pdwSlotLeast);
HRESULT BindToSlot(DWORD dwSlot, IShellFolder **ppsf);
HRESULT Clear(CMruPidlList *proot);
CMruNode *GetParent()
{ if (_pnodeParent) _pnodeParent->AddRef(); return _pnodeParent;}
HRESULT GetNodeSlot(DWORD *pdwNodeSlot)
{
DWORD cb = sizeof(*pdwNodeSlot);
return NOERROR == SHGetValue(_hkMru, NULL, L"NodeSlot", NULL, pdwNodeSlot, pdwNodeSlot ? &cb : NULL) ? S_OK : E_FAIL;
}
HRESULT SetNodeSlot(DWORD dwNodeSlot)
{ return NOERROR == SHSetValue(_hkMru, NULL, L"NodeSlot", REG_DWORD, &dwNodeSlot, sizeof(dwNodeSlot)) ? S_OK : E_FAIL; }
protected:
CMruNode() {}
virtual ~CMruNode();
virtual BOOL _IsEqual(SLOTITEMDATA *pitem, const BYTE *pData, DWORD cbData);
virtual void _DeleteValue(LPCWSTR psz);
HRESULT _GetPidlSlot(LPCITEMIDLIST pidlChild, BOOL fCreate, DWORD *pdwKidSlot);
HRESULT _CreateNode(DWORD dwSlot, CMruNode **ppnode);
BOOL _InitLate();
HRESULT _FindPidl(LPCITEMIDLIST pidl, int *piIndex)
{ return FindData((LPBYTE)pidl, pidl->mkid.cb + sizeof(pidl->mkid.cb), piIndex); }
HRESULT _AddPidl(DWORD dwSlot, LPCITEMIDLIST pidl)
{ return _AddItem(dwSlot, (LPBYTE)pidl, pidl->mkid.cb + sizeof(pidl->mkid.cb)); }
#ifdef DEBUG
HRESULT _GetSlotName(DWORD dwSlot, LPWSTR psz, DWORD cch);
#endif
protected:
DWORD _dwSlotSelf;
CMruNode *_pnodeParent;
IShellFolder *_psf;
};
class CMruPidlList : public CMruNode
, public IMruPidlList
{
public:
CMruPidlList() {}
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppvOut);
STDMETHODIMP_(ULONG) AddRef()
{
return CMruBase::AddRef();
}
STDMETHODIMP_(ULONG) Release()
{
return CMruBase::Release();
}
// IMruPidlList
STDMETHODIMP InitList(UINT uMax, HKEY hKey, LPCWSTR pszSubKey);
STDMETHODIMP UsePidl(LPCITEMIDLIST pidl, DWORD *pdwSlot);
STDMETHODIMP QueryPidl(LPCITEMIDLIST pidl, DWORD cSlots, DWORD *rgdwSlots, DWORD *pcSlotsFetched);
STDMETHODIMP PruneKids(LPCITEMIDLIST pidl);
HRESULT GetEmptySlot(DWORD *pdwSlot);
void EmptyNodeSlot(DWORD dwNodeSlot);
protected:
~CMruPidlList()
{
if (_rgbNodeSlots)
{
LocalFree(_rgbNodeSlots);
_rgbNodeSlots = NULL;
}
if (_hMutex)
CloseHandle(_hMutex);
}
BOOL _LoadNodeSlots();
void _SaveNodeSlots();
HRESULT _InitNodeSlots();
protected:
BYTE *_rgbNodeSlots;
int _cUsedNodeSlots ;
HANDLE _hMutex;
};
CMruNode::CMruNode(CMruNode *pnodeParent, DWORD dwSlot)
: _pnodeParent(pnodeParent), _dwSlotSelf(dwSlot)
{
ASSERT(_cRef);
_pnodeParent->AddRef();
}
CMruNode::~CMruNode()
{
if (_pnodeParent)
_pnodeParent->Release();
if (_psf)
_psf->Release();
}
HRESULT CMruNode::BindToSlot(DWORD dwSlot, IShellFolder **ppsf)
{
SLOTITEMDATA *pitem;
HRESULT hr = _GetSlotItem(dwSlot, &pitem);
if (SUCCEEDED(hr))
{
hr = _psf->BindToObject((LPCITEMIDLIST)pitem->p, NULL, IID_PPV_ARG(IShellFolder, ppsf));
}
return hr;
}
#ifdef DEBUG
HRESULT CMruNode::_GetSlotName(DWORD dwSlot, LPWSTR psz, DWORD cch)
{
SLOTITEMDATA *pitem;
HRESULT hr = _GetSlotItem(dwSlot, &pitem);
if (SUCCEEDED(hr))
{
hr = DisplayNameOf(_psf, (LPCITEMIDLIST)pitem->p, 0, psz, cch);
}
return hr;
}
#endif
BOOL CMruNode::_IsEqual(SLOTITEMDATA *pitem, const BYTE *pData, DWORD cbData)
{
return S_OK == IShellFolder_CompareIDs(_psf, SHCIDS_CANONICALONLY, (LPCITEMIDLIST)pitem->p, (LPCITEMIDLIST)pData);
}
HRESULT CMruNode::_CreateNode(DWORD dwSlot, CMruNode **ppnode)
{
HRESULT hr = E_OUTOFMEMORY;
CMruNode *pnode = new CMruNode(this, dwSlot);
if (pnode)
{
WCHAR szSlot[12];
_SlotString(dwSlot, szSlot, ARRAYSIZE(szSlot));
hr = pnode->InitData(_cMaxSlots, 0, _hkMru, szSlot, NULL);
if (SUCCEEDED(hr))
*ppnode = pnode;
else
pnode->Release();
}
return hr;
}
BOOL CMruNode::_InitLate()
{
if (!_psf)
{
if (_pnodeParent)
{
_pnodeParent->BindToSlot(_dwSlotSelf, &_psf);
#ifdef DEBUG
WCHAR sz[MAX_PATH];
if (SUCCEEDED(_pnodeParent->_GetSlotName(_dwSlotSelf, sz, ARRAYSIZE(sz))))
SHSetValue(_hkMru, NULL, L"SlotName", REG_SZ, sz, CbFromCchW(lstrlen(sz) + 1));
#endif
}
else
SHGetDesktopFolder(&_psf);
}
return (_psf != NULL);
}
HRESULT CMruNode::_GetPidlSlot(LPCITEMIDLIST pidlChild, BOOL fCreate, DWORD *pdwKidSlot)
{
HRESULT hr = E_OUTOFMEMORY;
LPITEMIDLIST pidlFirst = ILCloneFirst(pidlChild);
if (pidlFirst)
{
int iIndex;
if (SUCCEEDED(_FindPidl(pidlFirst, &iIndex)))
{
*pdwKidSlot = _UpdateSlots(iIndex);
hr = S_OK;
}
else if (fCreate)
{
*pdwKidSlot = _UpdateSlots(_cUsedSlots);
hr = _AddPidl(*pdwKidSlot, pidlFirst);
}
ILFree(pidlFirst);
}
return hr;
}
HRESULT CMruNode::GetNode(BOOL fCreate, LPCITEMIDLIST pidlChild, CMruNode **ppnode)
{
HRESULT hr = E_FAIL;
if (ILIsEmpty(pidlChild))
{
*ppnode = this;
AddRef();
hr = S_OK;
}
else if (_InitLate())
{
DWORD dwKidSlot;
hr = _GetPidlSlot(pidlChild, fCreate, &dwKidSlot);
if (SUCCEEDED(hr))
{
// need to make another CMruNode
CMruNode *pnode;
hr = _CreateNode(dwKidSlot, &pnode);
if (SUCCEEDED(hr))
{
// need to save so that this node
// is updated so that it doesnt get
// deleted from under us.
_SaveSlots();
hr = pnode->GetNode(fCreate, _ILNext(pidlChild), ppnode);
pnode->Release();
}
}
if (FAILED(hr) && !fCreate)
{
*ppnode = this;
AddRef();
hr = S_FALSE;
}
}
return hr;
}
void CMruNode::_DeleteValue(LPCWSTR psz)
{
CMruBase::_DeleteValue(psz);
SHDeleteKey(_hkMru, psz);
}
HRESULT CMruNode::RemoveLeast(DWORD *pdwSlotLeast)
{
// if this node has children
// then we attempt to call RemoveLeast on them
ASSERT(_cUsedSlots >= 0);
HRESULT hr = S_FALSE;
if (_cUsedSlots)
{
DWORD dwLocalSlot;
hr = _GetSlot(_cUsedSlots - 1, &dwLocalSlot);
if (SUCCEEDED(hr))
{
CMruNode *pnode;
hr = _CreateNode(dwLocalSlot, &pnode);
if (SUCCEEDED(hr))
{
hr = pnode->RemoveLeast(pdwSlotLeast);
pnode->Release();
}
// S_FALSE means that this node needs
// needs deleting. it is empty.
if (hr == S_FALSE)
{
Delete(_cUsedSlots - 1);
// if we still have kids, or have a NodeSlot
// then we dont want to be deleted
if (_cUsedSlots || SUCCEEDED(GetNodeSlot(NULL)))
hr = S_OK;
}
}
}
else
{
// this is the empty node
// delete me if you can
ASSERT(!*pdwSlotLeast);
GetNodeSlot(pdwSlotLeast);
}
return hr;
}
HRESULT CMruNode::Clear(CMruPidlList *proot)
{
DWORD dwLocalSlot;
while (SUCCEEDED(_GetSlot(0, &dwLocalSlot)))
{
CMruNode *pnode;
if (SUCCEEDED(_CreateNode(dwLocalSlot, &pnode)))
{
// tell the root about it
DWORD dwNodeSlot;
if (SUCCEEDED(pnode->GetNodeSlot(&dwNodeSlot)))
proot->EmptyNodeSlot(dwNodeSlot);
pnode->Clear(proot);
pnode->Release();
}
Delete(0);
}
return S_OK;
}
class CSafeMutex
{
public:
CSafeMutex() : _h(0) {}
~CSafeMutex() { if (_h) ReleaseMutex(_h); }
HRESULT Enter(HANDLE hMutex)
{
// this is usually done on the UI thread
// wait for half a second or dont bother
HRESULT hr;
DWORD dwWait = WaitForSingleObject(hMutex, 500);
if (dwWait == WAIT_OBJECT_0)
{
_h = hMutex;
hr = S_OK;
}
else
hr = E_FAIL;
return hr;
}
private:
HANDLE _h;
};
HRESULT CMruPidlList::UsePidl(LPCITEMIDLIST pidl, DWORD *pdwSlot)
{
CSafeMutex sm;
HRESULT hr = sm.Enter(_hMutex);
if (SUCCEEDED(hr))
{
CMruNode *pnode;
hr = GetNode(TRUE, pidl, &pnode);
*pdwSlot = 0;
if (SUCCEEDED(hr))
{
ASSERT(hr == S_OK);
hr = pnode->GetNodeSlot(pdwSlot);
if (FAILED(hr))
{
hr = GetEmptySlot(pdwSlot);
if (SUCCEEDED(hr))
{
hr = pnode->SetNodeSlot(*pdwSlot);
}
}
pnode->Release();
}
}
return hr;
}
HRESULT CMruPidlList::QueryPidl(LPCITEMIDLIST pidl, DWORD cSlots, DWORD *rgdwSlots, DWORD *pcSlotsFetched)
{
CSafeMutex sm;
HRESULT hr = sm.Enter(_hMutex);
if (SUCCEEDED(hr))
{
CMruNode *pnode;
hr = GetNode(FALSE, pidl, &pnode);
*pcSlotsFetched = 0;
if (SUCCEEDED(hr))
{
while (*pcSlotsFetched < cSlots && pnode)
{
CMruNode *pnodeParent = pnode->GetParent();
if (SUCCEEDED(pnode->GetNodeSlot(&rgdwSlots[*pcSlotsFetched])))
{
(*pcSlotsFetched)++;
}
else if (hr == S_OK && !*pcSlotsFetched)
{
// we found the exact node
// but we couldnt get the NodeSlot from it
hr = S_FALSE;
}
pnode->Release();
pnode = pnodeParent;
}
if (pnode)
pnode->Release();
}
if (SUCCEEDED(hr) && !*pcSlotsFetched)
hr = E_FAIL;
}
return hr;
}
HRESULT CMruPidlList::PruneKids(LPCITEMIDLIST pidl)
{
CSafeMutex sm;
HRESULT hr = sm.Enter(_hMutex);
if (SUCCEEDED(hr))
{
if (_LoadNodeSlots())
{
CMruNode *pnode;
hr = GetNode(FALSE, pidl, &pnode);
if (SUCCEEDED(hr))
{
if (hr == S_OK)
{
hr = pnode->Clear(this);
}
else
hr = E_FAIL;
pnode->Release();
}
_SaveNodeSlots();
}
}
return hr;
}
STDMETHODIMP CMruPidlList::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] = {
QITABENT(CMruPidlList, IMruPidlList), // IID_IMruDataList
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
HRESULT CMruPidlList::InitList(UINT uMax, HKEY hKey, LPCWSTR pszSubKey)
{
HRESULT hr = InitData(uMax, 0, hKey, pszSubKey, NULL);
if (SUCCEEDED(hr))
{
hr = _InitNodeSlots();
if (SUCCEEDED(hr))
{
_hMutex = CreateMutex(NULL, FALSE, TEXT("Shell.CMruPidlList"));
if (!_hMutex)
hr = ResultFromLastError();
}
}
return hr;
}
BOOL CMruPidlList::_LoadNodeSlots()
{
DWORD cb = (_cMaxSlots) * sizeof(_rgbNodeSlots[0]);
if (NOERROR == SHGetValue(_hkMru, NULL, L"NodeSlots", NULL, _rgbNodeSlots , &cb))
{
_cUsedNodeSlots = (cb / sizeof(_rgbNodeSlots[0]));
return TRUE;
}
return FALSE;
}
void CMruPidlList::_SaveNodeSlots()
{
SHSetValue(_hkMru, NULL, L"NodeSlots", REG_BINARY, _rgbNodeSlots , _cUsedNodeSlots);
}
HRESULT CMruPidlList::_InitNodeSlots()
{
HRESULT hr = E_OUTOFMEMORY;
DWORD cb = (_cMaxSlots) * sizeof(_rgbNodeSlots[0]);
_rgbNodeSlots = (BYTE *) LocalAlloc(LPTR, cb);
if (_rgbNodeSlots)
{
_LoadNodeSlots();
_fDirty = TRUE;
_SaveNodeSlots();
hr = S_OK;
}
return hr;
}
void CMruPidlList::EmptyNodeSlot(DWORD dwNodeSlot)
{
ASSERT(dwNodeSlot <= (DWORD)_cMaxSlots);
_rgbNodeSlots[dwNodeSlot-1] = FALSE;
_fDirty = TRUE;
}
HRESULT CMruPidlList::GetEmptySlot(DWORD *pdwSlot)
{
HRESULT hr = E_FAIL;
*pdwSlot = 0;
if (_LoadNodeSlots())
{
if (_cUsedNodeSlots < _cMaxSlots)
{
// then we can just use the next most natural
// node slot
_rgbNodeSlots[_cUsedNodeSlots] = SLOT_USED;
*pdwSlot = ++_cUsedNodeSlots;
hr = S_OK;
}
else
{
// if we can find an empty in the list...
for (int i = 0; i < _cUsedNodeSlots; i++)
{
if (!(_rgbNodeSlots[i] & SLOT_USED))
{
_rgbNodeSlots[i] = SLOT_USED;
*pdwSlot = i+1;
hr = S_OK;
break;
}
}
if (FAILED(hr))
{
// we need to find the LRU slot
if (SUCCEEDED(RemoveLeast(pdwSlot)) && *pdwSlot)
hr = S_OK;
}
}
_SaveNodeSlots();
}
return hr;
}
STDAPI CMruPidlList_CreateInstance(IUnknown * punkOuter, IUnknown ** ppunk, LPCOBJECTINFO poi)
{
*ppunk = NULL;
CMruPidlList *p = new CMruPidlList();
if (p != NULL)
{
*ppunk = SAFECAST(p, IMruPidlList *);
return S_OK;
}
return E_OUTOFMEMORY;
}