mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1190 lines
32 KiB
1190 lines
32 KiB
#include "dpastuff.h"
|
|
|
|
//
|
|
// The ORDERITEM structure is exposed via the IOrderList interface.
|
|
// ORDERITEM2 contains our private hidden fields.
|
|
//
|
|
// The extra fields contain information about the cached icon location.
|
|
//
|
|
// ftModified is the modify-time on the pidl, which is used to detect
|
|
// whether the cache needs to be refreshed.
|
|
//
|
|
// If ftModified is nonzero, then { pwszIcon, iIconIndex, pidlTarget }
|
|
// describe the icon that should be displayed for the item.
|
|
//
|
|
// If pwszIcon is nonzero, then the item is a shortcut with a custom
|
|
// icon. pwszIcon points to the file name for the icon, iIconIndex
|
|
// is the icon index within the pwszIcon file.
|
|
//
|
|
// If pidlTarget is nonzero, then the item is a shortcut with a default
|
|
// icon. pidlTarget is the target pidl, whose icon we should use.
|
|
//
|
|
|
|
typedef struct ORDERITEM2 {
|
|
ORDERITEM oi; // part that clients see - must come first
|
|
DWORD dwFlags; // User defined flags.
|
|
LPWSTR pwszIcon; // for cacheing the icon location
|
|
int iIconIndex; // for cacheing the icon location
|
|
LPITEMIDLIST pidlTarget; // use the icon for this pidl
|
|
} ORDERITEM2, *PORDERITEM2;
|
|
|
|
int CALLBACK OrderItem_Compare(LPVOID pv1, LPVOID pv2, LPARAM lParam)
|
|
{
|
|
PORDERITEM poi1 = (PORDERITEM)pv1;
|
|
PORDERITEM poi2 = (PORDERITEM)pv2;
|
|
PORDERINFO poinfo = (PORDERINFO)lParam;
|
|
int nRet;
|
|
|
|
if (!poinfo)
|
|
{
|
|
ASSERT(FALSE);
|
|
return 0;
|
|
}
|
|
switch (poinfo->dwSortBy)
|
|
{
|
|
case OI_SORTBYNAME:
|
|
{
|
|
// Make sure they're both non-null
|
|
//
|
|
if ( poi1->pidl && poi2->pidl )
|
|
{
|
|
HRESULT hres = poinfo->psf->CompareIDs(0, poi1->pidl, poi2->pidl);
|
|
nRet = (short)HRESULT_CODE(hres);
|
|
}
|
|
else
|
|
{
|
|
if ( poi1->pidl == poi2->pidl )
|
|
nRet = 0;
|
|
else
|
|
nRet = ((UINT_PTR)poi1->pidl < (UINT_PTR)poi2->pidl ? -1 : 1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OI_SORTBYORDINAL:
|
|
if (poi1->nOrder == poi2->nOrder)
|
|
nRet = 0;
|
|
else
|
|
// do unsigned compare so -1 goes to end of list
|
|
nRet = ((UINT)poi1->nOrder < (UINT)poi2->nOrder ? -1 : 1);
|
|
break;
|
|
|
|
default:
|
|
ASSERT_MSG(0, "Bad dwSortBy passed to OrderItem_Compare");
|
|
nRet = 0;
|
|
break;
|
|
}
|
|
|
|
return nRet;
|
|
}
|
|
|
|
void OrderItem_FreeIconInfo(PORDERITEM poi)
|
|
{
|
|
PORDERITEM2 poi2 = CONTAINING_RECORD(poi, ORDERITEM2, oi);
|
|
if (poi2->pwszIcon)
|
|
{
|
|
LPWSTR pwszIcon = poi2->pwszIcon;
|
|
poi2->pwszIcon = NULL;
|
|
LocalFree(pwszIcon);
|
|
}
|
|
|
|
if (poi2->pidlTarget)
|
|
{
|
|
LPITEMIDLIST pidl = poi2->pidlTarget;
|
|
poi2->pidlTarget = NULL;
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
|
|
|
|
LPVOID CALLBACK OrderItem_Merge(UINT uMsg, LPVOID pvDst, LPVOID pvSrc, LPARAM lParam)
|
|
{
|
|
PORDERITEM2 poi2Dst = CONTAINING_RECORD(pvDst, ORDERITEM2, oi);
|
|
PORDERITEM2 poi2Src = CONTAINING_RECORD(pvSrc, ORDERITEM2, oi);
|
|
PORDERINFO poinfo = (PORDERINFO)lParam;
|
|
LPVOID pvRet = pvDst;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case DPAMM_MERGE:
|
|
// Transfer the order field
|
|
poi2Dst->oi.nOrder = poi2Src->oi.nOrder;
|
|
|
|
// Propagate any cached icon information too...
|
|
if (poi2Src->pwszIcon || poi2Src->pidlTarget)
|
|
{
|
|
// To avoid useless allocation, we transfer the cache across
|
|
// instead of copying it.
|
|
if (poinfo->psf2 &&
|
|
poinfo->psf2->CompareIDs(SHCIDS_ALLFIELDS, poi2Dst->oi.pidl, poi2Src->oi.pidl) == S_OK)
|
|
{
|
|
OrderItem_FreeIconInfo(&poi2Dst->oi);
|
|
CopyMemory((LPBYTE)poi2Dst + sizeof(ORDERITEM),
|
|
(LPBYTE)poi2Src + sizeof(ORDERITEM),
|
|
sizeof(ORDERITEM2) - sizeof(ORDERITEM));
|
|
ZeroMemory((LPBYTE)poi2Src + sizeof(ORDERITEM),
|
|
sizeof(ORDERITEM2) - sizeof(ORDERITEM));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DPAMM_DELETE:
|
|
case DPAMM_INSERT:
|
|
// Don't need to implement this
|
|
ASSERT(0);
|
|
pvRet = NULL;
|
|
break;
|
|
}
|
|
|
|
return pvRet;
|
|
}
|
|
|
|
int OrderItem_UpdatePos(LPVOID p, LPVOID pData)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)p;
|
|
|
|
if (-1 == poi->nOrder)
|
|
{
|
|
poi->nOrder = (int)(INT_PTR)pData;
|
|
}
|
|
else if ((int)(INT_PTR)pData >= poi->nOrder)
|
|
{
|
|
poi->nOrder++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// OrderList_Merge sorts hdpaNew to match hdpaOld order,
|
|
// putting any items in hdpaNew that were not in hdpaOld
|
|
// at position iInsertPos (-1 means end of list).
|
|
//
|
|
// Assumes hdpaOld is already sorted by sort order in lParam (OI_SORTBYNAME by default)
|
|
// (if hdpaOld is specified)
|
|
//
|
|
void OrderList_Merge(HDPA hdpaNew, HDPA hdpaOld, int iInsertPos, LPARAM lParam,
|
|
LPFNORDERMERGENOMATCH pfn, LPVOID pvParam)
|
|
{
|
|
PORDERINFO poinfo = (PORDERINFO)lParam;
|
|
|
|
BOOL fMergeOnly = FALSE;
|
|
if (poinfo->dwSortBy == OI_MERGEBYNAME)
|
|
{
|
|
poinfo->dwSortBy = OI_SORTBYNAME;
|
|
fMergeOnly = TRUE;
|
|
}
|
|
|
|
// hdpaNew has not been sorted, sort by name
|
|
DPA_Sort(hdpaNew, OrderItem_Compare, lParam);
|
|
BOOL fForceNoMatch = FALSE;
|
|
|
|
if (FAILED(poinfo->psf->QueryInterface(IID_IShellFolder2, (LPVOID *)&poinfo->psf2))) {
|
|
// 239390: Network Connections folder doesn't implement QI correctly. Its psf
|
|
// fails QI for IID_IShellFolder2, but doesn't null out ppvObj. So do it for them.
|
|
poinfo->psf2 = NULL;
|
|
}
|
|
|
|
// Copy order preferences over from old list to new list
|
|
if (hdpaOld)
|
|
{
|
|
DPA_Merge(hdpaNew, hdpaOld, DPAM_SORTED | DPAM_NORMAL, OrderItem_Compare, OrderItem_Merge, lParam);
|
|
|
|
// If we're waiting for the notify from a drag&drop operation,
|
|
// update the new items (they will have a -1) to the insert position.
|
|
if (-1 != iInsertPos)
|
|
{
|
|
DPA_EnumCallback(hdpaNew, OrderItem_UpdatePos, (LPVOID)(INT_PTR)iInsertPos);
|
|
}
|
|
|
|
if (poinfo->dwSortBy != OI_SORTBYORDINAL && !fMergeOnly)
|
|
{
|
|
poinfo->dwSortBy = OI_SORTBYORDINAL;
|
|
DPA_Sort(hdpaNew, OrderItem_Compare, lParam);
|
|
}
|
|
}
|
|
else
|
|
fForceNoMatch = TRUE;
|
|
|
|
// If the caller passed a NoMatch callback, then call it with
|
|
// each item that is not matched.
|
|
if (pfn)
|
|
{
|
|
for (int i = DPA_GetPtrCount(hdpaNew)-1 ; i >= 0 ; i--)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)DPA_FastGetPtr(hdpaNew, i);
|
|
|
|
// Does this item have order information?
|
|
if (iInsertPos == poi->nOrder ||
|
|
-1 == poi->nOrder ||
|
|
fForceNoMatch)
|
|
{
|
|
// No; Then pass to the "No Match" callback
|
|
pfn(pvParam, poi->pidl);
|
|
}
|
|
}
|
|
}
|
|
|
|
ATOMICRELEASE(poinfo->psf2);
|
|
|
|
OrderList_Reorder(hdpaNew);
|
|
}
|
|
|
|
// OrderList_Reorder refreshes the order info
|
|
void OrderList_Reorder(HDPA hdpa)
|
|
{
|
|
int i;
|
|
|
|
for (i = DPA_GetPtrCount(hdpa)-1 ; i >= 0 ; i--)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)DPA_FastGetPtr(hdpa, i);
|
|
|
|
poi->nOrder = i;
|
|
}
|
|
}
|
|
|
|
BOOL OrderList_Append(HDPA hdpa, LPITEMIDLIST pidl, int nOrder)
|
|
{
|
|
PORDERITEM poi = OrderItem_Create(pidl, nOrder);
|
|
if (poi)
|
|
{
|
|
if (-1 != DPA_AppendPtr(hdpa, poi))
|
|
return TRUE;
|
|
|
|
OrderItem_Free(poi, FALSE); //don't free pidl because caller will do it
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// This differes from DPA_Clone in that it allocates new items!
|
|
HDPA OrderList_Clone(HDPA hdpa)
|
|
{
|
|
HDPA hdpaNew = NULL;
|
|
|
|
if (EVAL(hdpa))
|
|
{
|
|
hdpaNew = DPA_Create(DPA_GetPtrCount(hdpa));
|
|
|
|
if (hdpaNew)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0 ; i < DPA_GetPtrCount(hdpa) ; i++)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)DPA_FastGetPtr(hdpa, i);
|
|
LPITEMIDLIST pidl = ILClone(poi->pidl);
|
|
if (pidl)
|
|
{
|
|
if (!OrderList_Append(hdpaNew, pidl, poi->nOrder))
|
|
{
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return hdpaNew;
|
|
}
|
|
|
|
// Does not clone the pidl but will free it.
|
|
// Does not addref the psf nor release it.
|
|
PORDERITEM OrderItem_Create(LPITEMIDLIST pidl, int nOrder)
|
|
{
|
|
PORDERITEM2 poi = (PORDERITEM2)LocalAlloc(LPTR, SIZEOF(ORDERITEM2));
|
|
|
|
if (poi)
|
|
{
|
|
poi->oi.pidl = pidl;
|
|
poi->oi.nOrder = nOrder;
|
|
return &poi->oi;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void OrderItem_Free(PORDERITEM poi, BOOL fKillPidls /* = TRUE */)
|
|
{
|
|
if (fKillPidls)
|
|
ILFree(poi->pidl);
|
|
OrderItem_FreeIconInfo(poi);
|
|
LocalFree(poi);
|
|
}
|
|
|
|
int OrderItem_FreeItem(LPVOID p, LPVOID pData)
|
|
{
|
|
PORDERITEM poi = (PORDERITEM)p;
|
|
|
|
OrderItem_Free(poi, (BOOL)(INT_PTR)pData);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void OrderList_Destroy(HDPA* phdpa, BOOL fKillPidls /* = fTrue */)
|
|
{
|
|
if (*phdpa) {
|
|
DPA_DestroyCallback(*phdpa, OrderItem_FreeItem, (LPVOID) (INT_PTR)fKillPidls);
|
|
*phdpa = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return values:
|
|
//
|
|
// S_OK - icon obtained successfully
|
|
// S_FALSE - icon not obtained, don't waste time trying
|
|
// E_FAIL - no cached icon, need to do more work
|
|
//
|
|
HRESULT OrderItem_GetSystemImageListIndexFromCache(PORDERITEM poi,
|
|
IShellFolder *psf, int *piOut)
|
|
{
|
|
PORDERITEM2 poi2 = CONTAINING_RECORD(poi, ORDERITEM2, oi);
|
|
IShellFolder *psfT;
|
|
LPCITEMIDLIST pidlItem;
|
|
HRESULT hr;
|
|
|
|
// Do we have a cached icon location?
|
|
if (poi2->pwszIcon)
|
|
{
|
|
*piOut = 0;
|
|
// Validate Path existance.
|
|
if (PathFileExistsW(poi2->pwszIcon))
|
|
{
|
|
*piOut = Shell_GetCachedImageIndex(poi2->pwszIcon, poi2->iIconIndex, GIL_PERINSTANCE);
|
|
}
|
|
|
|
return (*piOut > 0)? S_OK : E_FAIL;
|
|
}
|
|
|
|
// Do we have a cached pidlTarget?
|
|
if (poi2->pidlTarget)
|
|
{
|
|
hr = SHBindToIDListParent(poi2->pidlTarget, IID_IShellFolder, (void**)&psfT, &pidlItem);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Make sure the pidl exsists before binding. because the bind does succeed if it does not exist.
|
|
DWORD dwAttrib = SFGAO_VALIDATE;
|
|
hr = psfT->GetAttributesOf(1, (LPCITEMIDLIST*)&pidlItem, &dwAttrib);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
*piOut = SHMapPIDLToSystemImageListIndex(psfT, pidlItem, NULL);
|
|
}
|
|
psfT->Release();
|
|
return hr;
|
|
}
|
|
|
|
// Bind failed - shortcut target was deleted
|
|
// Keep the cache valid because we don't want to whack the disk
|
|
// all the time only to discover it's busted.
|
|
return E_FAIL;
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
DWORD OrderItem_GetFlags(PORDERITEM poi)
|
|
{
|
|
PORDERITEM2 poi2 = CONTAINING_RECORD(poi, ORDERITEM2, oi);
|
|
return poi2->dwFlags;
|
|
}
|
|
|
|
void OrderItem_SetFlags(PORDERITEM poi, DWORD dwFlags)
|
|
{
|
|
PORDERITEM2 poi2 = CONTAINING_RECORD(poi, ORDERITEM2, oi);
|
|
poi2->dwFlags = dwFlags;
|
|
}
|
|
|
|
|
|
int OrderItem_GetSystemImageListIndex(PORDERITEM poi, IShellFolder *psf, BOOL fUseCache)
|
|
{
|
|
PORDERITEM2 poi2 = CONTAINING_RECORD(poi, ORDERITEM2, oi);
|
|
HRESULT hr;
|
|
int iBitmap;
|
|
DWORD dwAttr;
|
|
|
|
if (fUseCache)
|
|
{
|
|
hr = OrderItem_GetSystemImageListIndexFromCache(poi, psf, &iBitmap);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
return iBitmap;
|
|
}
|
|
else
|
|
{
|
|
goto Fallback;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Free any pointers we cached previously
|
|
//
|
|
if (poi2->pidlTarget)
|
|
{
|
|
ILFree(poi2->pidlTarget);
|
|
poi2->pidlTarget = NULL;
|
|
}
|
|
|
|
Str_SetPtr(&poi2->pwszIcon, NULL);
|
|
}
|
|
|
|
//
|
|
// Go find the icon.
|
|
//
|
|
ASSERT(poi2->pidlTarget == NULL);
|
|
ASSERT(poi2->pwszIcon == NULL);
|
|
|
|
//
|
|
// Is this item shortcutlike at all?
|
|
//
|
|
dwAttr = SFGAO_LINK;
|
|
hr = psf->GetAttributesOf(1, (LPCITEMIDLIST*)&poi->pidl, &dwAttr);
|
|
if (FAILED(hr) || !(dwAttr & SFGAO_LINK))
|
|
goto Fallback; // not a shortcut; use the fallback
|
|
|
|
//
|
|
// Must go for ANSI version first because client might not support
|
|
// UNICODE.
|
|
//
|
|
// FEATURE - should QI for IExtractIcon to see if we get GIL_DONTCACHE
|
|
// back.
|
|
|
|
IShellLinkA *pslA;
|
|
hr = psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&poi->pidl,
|
|
IID_IShellLinkA, 0, (LPVOID *)&pslA);
|
|
|
|
if (FAILED(hr))
|
|
goto Fallback;
|
|
|
|
//
|
|
// If there's a UNICODE version, that's even better.
|
|
//
|
|
IShellLinkW *pslW;
|
|
WCHAR wszIconPath[MAX_PATH];
|
|
|
|
hr = pslA->QueryInterface(IID_IShellLinkW, (LPVOID *)&pslW);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pslW->GetIconLocation(wszIconPath, ARRAYSIZE(wszIconPath), &poi2->iIconIndex);
|
|
pslW->Release();
|
|
}
|
|
else
|
|
{
|
|
// Only IShellLinkA supported. Thunk to UNICODE manually.
|
|
CHAR szIconPath[ARRAYSIZE(wszIconPath)];
|
|
hr = pslA->GetIconLocation(szIconPath, ARRAYSIZE(szIconPath), &poi2->iIconIndex);
|
|
if (SUCCEEDED(hr))
|
|
SHAnsiToUnicode(szIconPath, wszIconPath, ARRAYSIZE(wszIconPath));
|
|
}
|
|
|
|
// If we have a custom icon path, then save that
|
|
if (SUCCEEDED(hr) && wszIconPath[0])
|
|
{
|
|
Str_SetPtr(&poi2->pwszIcon, wszIconPath);
|
|
}
|
|
else
|
|
{
|
|
// No icon path, get the target instead
|
|
pslA->GetIDList(&poi2->pidlTarget);
|
|
|
|
if (IsURLChild(poi2->pidlTarget, TRUE))
|
|
{
|
|
// If this is a url, we want to go to the "Fallback" case. The reason for this
|
|
// is that the fallback case will go through
|
|
// where we will end up with the generic icon for .url files
|
|
ILFree(poi2->pidlTarget);
|
|
poi2->pidlTarget = NULL;
|
|
|
|
pslA->Release();
|
|
goto Fallback;
|
|
}
|
|
}
|
|
|
|
pslA->Release();
|
|
|
|
//
|
|
// Aw-right, the cache is all loaded up. Let's try that again.
|
|
//
|
|
hr = OrderItem_GetSystemImageListIndexFromCache(poi, psf, &iBitmap);
|
|
if (hr == S_OK)
|
|
{
|
|
return iBitmap;
|
|
}
|
|
|
|
Fallback:
|
|
return SHMapPIDLToSystemImageListIndex(psf, poi->pidl, NULL);
|
|
}
|
|
|
|
|
|
// Header for file menu streams
|
|
//
|
|
// The file menu stream consists of an IOSTREAMHEADER followed by
|
|
// a DPA_SaveStream of the order DPA. Each item in the DPA consists
|
|
// of an OISTREAMITEM.
|
|
//
|
|
// To keep roaming profiles working between NT4 (IE4) and NT5 (IE5),
|
|
// the dwVersion used by NT5 must be the same as that used by NT4.
|
|
// I.e., it must be 2.
|
|
|
|
typedef struct tagOISTREAMHEADER
|
|
{
|
|
DWORD cbSize; // Size of header
|
|
DWORD dwVersion; // Version of header
|
|
} OISTREAMHEADER;
|
|
|
|
#define OISTREAMHEADER_VERSION 2
|
|
|
|
//
|
|
// Each item in a persisted order DPA consists of an OISTREAMITEM
|
|
// followed by additional goo. All pidls stored include the
|
|
// terminating (USHORT)0.
|
|
//
|
|
// IE4:
|
|
// OISTREAMITEM
|
|
// pidl - the item itself
|
|
//
|
|
// IE5 - shortcut has custom icon
|
|
// OISTREAMITEM
|
|
// pidl - the item itself (last-modify time implied)
|
|
// <optional padding> - for WCHAR alignment
|
|
// dwFlags - User defined Flags
|
|
// dwStringLen - Length of the icon path
|
|
// UNICODEZ iconpath - icon path
|
|
// iIconIndex - icon index
|
|
//
|
|
// IE5 - shortcut takes its icon from another pidl
|
|
// OISTREAMITEM
|
|
// pidl - the item itself (last-modify time implied)
|
|
// <optional padding> - for WCHAR alignment
|
|
// dwFlags - User defined Flags
|
|
// (DWORD)0 - null string indicates "no custom icon"
|
|
// pidlTarget - use the icon for this pidl
|
|
//
|
|
|
|
typedef struct tagOISTREAMITEM
|
|
{
|
|
DWORD cbSize; // Size including trailing goo
|
|
int nOrder; // User-specified order
|
|
|
|
// variable-sized trailing goo comes here.
|
|
//
|
|
// See above for description of trailing goo.
|
|
|
|
} OISTREAMITEM;
|
|
|
|
#define CB_OISTREAMITEM (sizeof(OISTREAMITEM))
|
|
|
|
//
|
|
// Save a component of the orderitem to the stream. If an error has
|
|
// already occurred on the stream, *phrRc contains the old error code,
|
|
// and we write nothing.
|
|
//
|
|
// If pstm == NULL, then we are not actually writing anything. We are
|
|
// merely doing a dry run.
|
|
//
|
|
// Otherwise, *phrRc accumulates the number of bytes actually written,
|
|
// or receives an error code on failure.
|
|
//
|
|
|
|
void
|
|
OrderItem_SaveSubitemToStream(IStream *pstm, LPCVOID pvData, ULONG cb, HRESULT* phrRc)
|
|
{
|
|
HRESULT hres;
|
|
|
|
if (SUCCEEDED(*phrRc))
|
|
{
|
|
if (pstm)
|
|
{
|
|
hres = IStream_Write(pstm, (LPVOID)pvData, cb);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
*phrRc += cb; // successful write - accumulate
|
|
}
|
|
else
|
|
{
|
|
*phrRc = hres; // error - return error code
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*phrRc += cb; // no output stream - accumulate
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// This worker function (1) computes the numer of bytes we will actually
|
|
// write out, and (2) actually writes it if pstm != NULL.
|
|
//
|
|
// Return value is the number of bytes written (or would have been
|
|
// written), or a COM error code on failure.
|
|
//
|
|
|
|
const BYTE c_Zeros[2] = { 0 }; // a bunch of zeros
|
|
|
|
HRESULT
|
|
OrderItem_SaveToStreamWorker(PORDERITEM2 poi2, OISTREAMITEM *posi,
|
|
IStream *pstm, IShellFolder2 *psf2)
|
|
{
|
|
HRESULT hrRc = 0; // no bytes, no error
|
|
|
|
ASSERT(poi2->oi.pidl);
|
|
|
|
//
|
|
// First comes the header.
|
|
//
|
|
OrderItem_SaveSubitemToStream(pstm, posi, CB_OISTREAMITEM, &hrRc);
|
|
|
|
//
|
|
// Then the pidl.
|
|
//
|
|
|
|
// We're assuming this is an immediate child pidl. If it's not,
|
|
// the pidl is being truncated!
|
|
ASSERT(0 == _ILNext(poi2->oi.pidl)->mkid.cb);
|
|
|
|
OrderItem_SaveSubitemToStream(pstm, poi2->oi.pidl,
|
|
poi2->oi.pidl->mkid.cb + sizeof(USHORT),
|
|
&hrRc);
|
|
// Insert padding to get back to WCHAR alignment.
|
|
if (hrRc % sizeof(WCHAR))
|
|
{
|
|
OrderItem_SaveSubitemToStream(pstm, &c_Zeros, 1, &hrRc);
|
|
}
|
|
|
|
OrderItem_SaveSubitemToStream(pstm, &poi2->dwFlags, sizeof(DWORD), &hrRc);
|
|
|
|
//
|
|
// If we haven't barfed yet and the IShellFolder supports identity
|
|
// and there is icon information, then save it.
|
|
//
|
|
if (SUCCEEDED(hrRc) && psf2 && (poi2->pwszIcon || poi2->pidlTarget))
|
|
{
|
|
// Optional icon is present.
|
|
|
|
if (poi2->pwszIcon)
|
|
{
|
|
// UNICODEZ path
|
|
DWORD cbString = (lstrlenW(poi2->pwszIcon) + 1) * sizeof(WCHAR);
|
|
|
|
// Save the String len
|
|
OrderItem_SaveSubitemToStream(pstm, &cbString,
|
|
sizeof(DWORD) , &hrRc);
|
|
|
|
OrderItem_SaveSubitemToStream(pstm, poi2->pwszIcon,
|
|
(lstrlenW(poi2->pwszIcon) + 1) * sizeof(WCHAR), &hrRc);
|
|
|
|
// icon index
|
|
OrderItem_SaveSubitemToStream(pstm, &poi2->iIconIndex,
|
|
sizeof(poi2->iIconIndex), &hrRc);
|
|
}
|
|
else
|
|
{
|
|
DWORD cbString = 0;
|
|
OrderItem_SaveSubitemToStream(pstm, &cbString, sizeof(DWORD), &hrRc);
|
|
|
|
// pidlTarget
|
|
OrderItem_SaveSubitemToStream(pstm, poi2->pidlTarget,
|
|
ILGetSize(poi2->pidlTarget), &hrRc);
|
|
}
|
|
}
|
|
return hrRc;
|
|
}
|
|
|
|
HRESULT
|
|
CALLBACK
|
|
OrderItem_SaveToStream(DPASTREAMINFO * pinfo, IStream * pstm, LPVOID pvData)
|
|
{
|
|
PORDERITEM2 poi2 = (PORDERITEM2)pinfo->pvItem;
|
|
HRESULT hres = S_FALSE;
|
|
IShellFolder2 *psf2 = (IShellFolder2 *)pvData;
|
|
|
|
if (poi2->oi.pidl)
|
|
{
|
|
OISTREAMITEM osi;
|
|
|
|
// First a dry run to compute the size of this item.
|
|
hres = OrderItem_SaveToStreamWorker(poi2, NULL, NULL, psf2);
|
|
|
|
// Nothing actually got written, so this should always succeed.
|
|
ASSERT(SUCCEEDED(hres));
|
|
|
|
osi.cbSize = hres;
|
|
osi.nOrder = poi2->oi.nOrder;
|
|
|
|
// Now write it out for real
|
|
hres = OrderItem_SaveToStreamWorker(poi2, &osi, pstm, psf2);
|
|
|
|
// On success, we must return exactly S_OK or DPA will blow us off
|
|
if (SUCCEEDED(hres))
|
|
hres = S_OK;
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
//
|
|
// Check if a pidl we read out of a stream is a simple child pidl.
|
|
// The pidl must be exactly cb bytes in length.
|
|
// The pointer is known to be valid;
|
|
// we just want to check that the contents are good, too.
|
|
//
|
|
BOOL
|
|
IsValidPersistedChildPidl(LPCITEMIDLIST pidl, UINT cb)
|
|
{
|
|
// Must have at least room for one byte of pidl plus the terminating
|
|
// zero.
|
|
if (cb < 1 + sizeof(USHORT))
|
|
return FALSE;
|
|
|
|
// Make sure size is at least what it's supposed to be.
|
|
if (pidl->mkid.cb + sizeof(USHORT) > cb)
|
|
return FALSE;
|
|
|
|
// Make sure there's a zero right after it.
|
|
pidl = _ILNext(pidl);
|
|
return pidl->mkid.cb == 0;
|
|
}
|
|
|
|
//
|
|
// Just like ILGetSize, but returns (UINT)-1 if the pidl is corrupt.
|
|
// We use (UINT)-1 as the return value because it will be bigger than
|
|
// the buffer size we eventually compare it against.
|
|
UINT SafeILGetSize(LPCITEMIDLIST pidl)
|
|
{
|
|
__try
|
|
{
|
|
return ILGetSize(pidl);
|
|
}
|
|
_except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
}
|
|
return (UINT)-1;
|
|
}
|
|
|
|
HRESULT
|
|
CALLBACK
|
|
OrderItem_LoadFromStream(DPASTREAMINFO * pinfo, IStream * pstm, LPVOID /*pvData*/)
|
|
{
|
|
HRESULT hres;
|
|
OISTREAMITEM osi;
|
|
|
|
hres = IStream_Read(pstm, &osi, CB_OISTREAMITEM);
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
ASSERT(CB_OISTREAMITEM < osi.cbSize);
|
|
if (CB_OISTREAMITEM < osi.cbSize)
|
|
{
|
|
UINT cb = osi.cbSize - CB_OISTREAMITEM;
|
|
LPITEMIDLIST pidl = IEILCreate(cb);
|
|
if ( !pidl )
|
|
hres = E_OUTOFMEMORY;
|
|
else
|
|
{
|
|
hres = IStream_Read(pstm, pidl, cb);
|
|
if (SUCCEEDED(hres) && IsValidPersistedChildPidl(pidl, cb))
|
|
{
|
|
PORDERITEM poi = OrderItem_Create(pidl, osi.nOrder);
|
|
|
|
if (poi)
|
|
{
|
|
PORDERITEM2 poi2 = CONTAINING_RECORD(poi, ORDERITEM2, oi);
|
|
pinfo->pvItem = poi;
|
|
// cbPos = offset to trailing gunk after pidl
|
|
UINT cbPos = pidl->mkid.cb + sizeof(USHORT);
|
|
cbPos = ROUNDUP(cbPos, sizeof(WCHAR));
|
|
|
|
// Do we have a DWORD hanging off the end of the pidl? This should be the flags.
|
|
if (cb >= cbPos + sizeof(DWORD))
|
|
{
|
|
poi2->dwFlags = *(UNALIGNED DWORD*)((LPBYTE)pidl + cbPos);
|
|
}
|
|
|
|
// Make sure there's at least a WCHAR to test against.
|
|
if (cb >= cbPos + sizeof(WCHAR) + 2 * sizeof(DWORD))
|
|
{
|
|
DWORD cbString = *(UNALIGNED DWORD*)((LPBYTE)pidl + cbPos + sizeof(DWORD));
|
|
LPWSTR pwszIcon = (LPWSTR)((LPBYTE)pidl + cbPos + 2 * sizeof(DWORD));
|
|
|
|
// Do we have a string lenght?
|
|
if (pwszIcon && cbString != 0)
|
|
{
|
|
// Yes, then this is a string not a pidl. We want to make sure this is a
|
|
// fully qualified path.
|
|
if (IS_VALID_STRING_PTRW(pwszIcon, cbString) &&
|
|
!PathIsRelative(pwszIcon))
|
|
{
|
|
poi2->pwszIcon = StrDup(pwszIcon);
|
|
pwszIcon += lstrlenW(pwszIcon) + 1;
|
|
poi2->iIconIndex = *(UNALIGNED int *)pwszIcon;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// A string length of zero is
|
|
LPITEMIDLIST pidlTarget = (LPITEMIDLIST)(pwszIcon);
|
|
// We want to write
|
|
// cbPos + sizeof(WCHAR) + SafeILGetSize(pidlTarget) <= cb
|
|
// but SafeILGetSize returns (UINT)-1 on error, so we need
|
|
// to do some algebra to avoid overflows
|
|
if (SafeILGetSize(pidlTarget) <= cb - cbPos - 2 * sizeof(DWORD))
|
|
{
|
|
poi2->pidlTarget = ILClone(pidlTarget);
|
|
}
|
|
}
|
|
}
|
|
|
|
hres = E_OUTOFMEMORY;
|
|
|
|
// pidl Contains extranious information. Take the hit of stripping it so that
|
|
// our working set doesn't bloat.
|
|
LPITEMIDLIST pidlNew = ILClone(poi2->oi.pidl);
|
|
if (pidlNew)
|
|
{
|
|
ILFree(poi2->oi.pidl);
|
|
poi2->oi.pidl = pidlNew;
|
|
hres = S_OK;
|
|
}
|
|
}
|
|
else
|
|
hres = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
hres = E_FAIL;
|
|
|
|
// Cleanup
|
|
if (FAILED(hres))
|
|
ILFree(pidl);
|
|
}
|
|
}
|
|
else
|
|
hres = E_FAIL;
|
|
|
|
}
|
|
|
|
ASSERT((S_OK == hres && pinfo->pvItem) || FAILED(hres));
|
|
return hres;
|
|
}
|
|
|
|
HRESULT OrderList_LoadFromStream(IStream* pstm, HDPA * phdpa, IShellFolder * psfParent)
|
|
{
|
|
HDPA hdpa = NULL;
|
|
OISTREAMHEADER oish;
|
|
|
|
ASSERT(phdpa);
|
|
ASSERT(pstm);
|
|
|
|
// Read the header for more info
|
|
if (SUCCEEDED(IStream_Read(pstm, &oish, sizeof(oish))) &&
|
|
sizeof(oish) == oish.cbSize)
|
|
{
|
|
// Load the stream. (Should be ordered by name.)
|
|
DPA_LoadStream(&hdpa, OrderItem_LoadFromStream, pstm, psfParent);
|
|
|
|
// if this is the wrong version, throw away the pidls.
|
|
// we go through the load anyways to make suret he read pointer is set right
|
|
if (OISTREAMHEADER_VERSION != oish.dwVersion)
|
|
OrderList_Destroy(&hdpa, TRUE);
|
|
|
|
}
|
|
|
|
*phdpa = hdpa;
|
|
|
|
return (NULL != hdpa) ? S_OK : E_FAIL;
|
|
}
|
|
|
|
HRESULT OrderList_SaveToStream(IStream* pstm, HDPA hdpaSave, IShellFolder *psf)
|
|
{
|
|
HRESULT hres = E_OUTOFMEMORY;
|
|
OISTREAMHEADER oish;
|
|
HDPA hdpa;
|
|
|
|
// Clone the array and sort by name for the purpose of persisting it
|
|
hdpa = DPA_Clone(hdpaSave, NULL);
|
|
if (hdpa)
|
|
{
|
|
ORDERINFO oinfo = {0};
|
|
#ifdef DEBUG
|
|
// use QI to help track down leaks
|
|
if (psf)
|
|
EVAL(SUCCEEDED(psf->QueryInterface(IID_IShellFolder, (LPVOID *)&oinfo.psf)));
|
|
#else
|
|
oinfo.psf = psf;
|
|
if (psf)
|
|
oinfo.psf->AddRef();
|
|
#endif
|
|
oinfo.dwSortBy = OI_SORTBYNAME;
|
|
DPA_Sort(hdpa, OrderItem_Compare, (LPARAM)&oinfo);
|
|
|
|
// Save the header
|
|
oish.cbSize = sizeof(oish);
|
|
oish.dwVersion = OISTREAMHEADER_VERSION;
|
|
|
|
hres = IStream_Write(pstm, &oish, sizeof(oish));
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
if (psf)
|
|
oinfo.psf->QueryInterface(IID_IShellFolder2, (LPVOID *)&oinfo.psf2);
|
|
hres = DPA_SaveStream(hdpa, OrderItem_SaveToStream, pstm, oinfo.psf2);
|
|
ATOMICRELEASE(oinfo.psf2);
|
|
}
|
|
ATOMICRELEASE(oinfo.psf);
|
|
DPA_Destroy(hdpa);
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
/////////////
|
|
//
|
|
// COrderList impl for export to channel installer
|
|
//
|
|
|
|
class COrderList : public IPersistFolder,
|
|
public IOrderList2
|
|
{
|
|
public:
|
|
virtual STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
|
|
virtual STDMETHODIMP_(ULONG) AddRef(void);
|
|
virtual STDMETHODIMP_(ULONG) Release(void);
|
|
|
|
// IPersistFolder
|
|
virtual STDMETHODIMP GetClassID(CLSID *pClassID);
|
|
virtual STDMETHODIMP Initialize(LPCITEMIDLIST pidl);
|
|
|
|
// IOrderList
|
|
virtual STDMETHODIMP GetOrderList(HDPA * phdpa);
|
|
virtual STDMETHODIMP SetOrderList(HDPA hdpa, IShellFolder *psf);
|
|
virtual STDMETHODIMP FreeOrderList(HDPA hdpa);
|
|
|
|
virtual STDMETHODIMP SortOrderList(HDPA hdpa, DWORD dw);
|
|
|
|
virtual STDMETHODIMP AllocOrderItem(PORDERITEM * ppoi, LPCITEMIDLIST pidl);
|
|
virtual STDMETHODIMP FreeOrderItem(PORDERITEM poi);
|
|
|
|
// IOrderList 2
|
|
virtual STDMETHODIMP LoadFromStream(IStream* pstm, HDPA* hdpa, IShellFolder* psf);
|
|
virtual STDMETHODIMP SaveToStream(IStream* pstm, HDPA hdpa);
|
|
|
|
protected:
|
|
COrderList(IUnknown* punkOuter, LPCOBJECTINFO poi);
|
|
friend IUnknown * COrderList_Create();
|
|
|
|
COrderList();
|
|
~COrderList();
|
|
|
|
int _cRef;
|
|
IShellFolder *_psf;
|
|
LPITEMIDLIST _pidl;
|
|
LPITEMIDLIST _pidlFavorites;
|
|
};
|
|
|
|
COrderList::COrderList()
|
|
{
|
|
_cRef = 1;
|
|
DllAddRef();
|
|
}
|
|
|
|
COrderList::~COrderList()
|
|
{
|
|
ILFree(_pidl);
|
|
ILFree(_pidlFavorites);
|
|
ATOMICRELEASE(_psf);
|
|
DllRelease();
|
|
}
|
|
|
|
IUnknown * COrderList_Create()
|
|
{
|
|
COrderList * pcol = new COrderList;
|
|
if (pcol)
|
|
{
|
|
return SAFECAST(pcol, IPersistFolder*);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
STDAPI COrderList_CreateInstance(IUnknown * pUnkOuter, IUnknown ** punk, LPCOBJECTINFO poi)
|
|
{
|
|
*punk = COrderList_Create();
|
|
|
|
return *punk ? S_OK : E_OUTOFMEMORY;
|
|
}
|
|
|
|
ULONG COrderList::AddRef()
|
|
{
|
|
_cRef++;
|
|
return _cRef;
|
|
}
|
|
|
|
ULONG COrderList::Release()
|
|
{
|
|
ASSERT(_cRef > 0);
|
|
_cRef--;
|
|
|
|
if (_cRef > 0)
|
|
return _cRef;
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
HRESULT COrderList::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(COrderList, IPersistFolder),
|
|
QITABENT(COrderList, IOrderList),
|
|
QITABENTMULTI(COrderList, IOrderList2, IOrderList),
|
|
{ 0 },
|
|
};
|
|
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
HRESULT COrderList::GetClassID(CLSID *pClassID)
|
|
{
|
|
*pClassID = CLSID_OrderListExport;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// This is the directory setup wants to re-order
|
|
HRESULT COrderList::Initialize(LPCITEMIDLIST pidl)
|
|
{
|
|
if (!_pidlFavorites)
|
|
{
|
|
SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &_pidlFavorites);
|
|
if (!_pidlFavorites)
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (!pidl || !ILIsParent(_pidlFavorites, pidl, FALSE))
|
|
return E_INVALIDARG;
|
|
|
|
// Initialize can be called multiple times
|
|
ATOMICRELEASE(_psf);
|
|
|
|
Pidl_Set(&_pidl, pidl);
|
|
|
|
if (_pidl)
|
|
IEBindToObject(_pidl, &_psf);
|
|
|
|
if (!_psf)
|
|
return E_OUTOFMEMORY;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT COrderList_GetOrderList(HDPA * phdpa, LPCITEMIDLIST pidl, IShellFolder * psf)
|
|
{
|
|
IStream* pstm = OpenPidlOrderStream((LPCITEMIDLIST)CSIDL_FAVORITES, pidl, REG_SUBKEY_FAVORITESA, STGM_READ);
|
|
if (pstm)
|
|
{
|
|
HRESULT hres = OrderList_LoadFromStream(pstm, phdpa, psf);
|
|
pstm->Release();
|
|
return hres;
|
|
}
|
|
*phdpa = NULL;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
HRESULT COrderList::GetOrderList(HDPA * phdpa)
|
|
{
|
|
HRESULT hres = E_FAIL;
|
|
|
|
*phdpa = NULL;
|
|
|
|
if (_psf)
|
|
hres = COrderList_GetOrderList(phdpa, _pidl, _psf);
|
|
|
|
return hres;
|
|
}
|
|
|
|
HRESULT COrderList_SetOrderList(HDPA hdpa, LPCITEMIDLIST pidl, IShellFolder *psf)
|
|
{
|
|
IStream* pstm = OpenPidlOrderStream((LPCITEMIDLIST)CSIDL_FAVORITES, pidl, REG_SUBKEY_FAVORITESA, STGM_WRITE);
|
|
if (EVAL(pstm))
|
|
{
|
|
HRESULT hres = OrderList_SaveToStream(pstm, hdpa, psf);
|
|
pstm->Release();
|
|
return hres;
|
|
}
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
HRESULT COrderList::SetOrderList(HDPA hdpa, IShellFolder *psf)
|
|
{
|
|
if (!_psf)
|
|
return E_FAIL;
|
|
|
|
return COrderList_SetOrderList(hdpa, _pidl, psf);
|
|
}
|
|
|
|
HRESULT COrderList::FreeOrderList(HDPA hdpa)
|
|
{
|
|
OrderList_Destroy(&hdpa);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT COrderList::SortOrderList(HDPA hdpa, DWORD dw)
|
|
{
|
|
if (OI_SORTBYNAME != dw && OI_SORTBYORDINAL != dw)
|
|
return E_INVALIDARG;
|
|
|
|
if (!_psf)
|
|
return E_FAIL;
|
|
|
|
ORDERINFO oinfo;
|
|
oinfo.dwSortBy = dw;
|
|
oinfo.psf = _psf;
|
|
#ifdef DEBUG
|
|
oinfo.psf2 = (IShellFolder2 *)INVALID_HANDLE_VALUE; // force fault if someone uses it
|
|
#endif
|
|
|
|
DPA_Sort(hdpa, OrderItem_Compare, (LPARAM)&oinfo);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT COrderList::AllocOrderItem(PORDERITEM * ppoi, LPCITEMIDLIST pidl)
|
|
{
|
|
LPITEMIDLIST pidlClone = ILClone(pidl);
|
|
|
|
*ppoi = NULL;
|
|
|
|
if (pidlClone)
|
|
{
|
|
*ppoi = OrderItem_Create(pidlClone, -1);
|
|
if (*ppoi)
|
|
return S_OK;
|
|
|
|
ILFree(pidlClone);
|
|
}
|
|
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
|
|
|
|
HRESULT COrderList::FreeOrderItem(PORDERITEM poi)
|
|
{
|
|
OrderItem_Free(poi);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// IOrderList2::LoadFromStream
|
|
STDMETHODIMP COrderList::LoadFromStream(IStream* pstm, HDPA* phdpa, IShellFolder* psf)
|
|
{
|
|
ASSERT(_psf == NULL);
|
|
_psf = psf;
|
|
if (_psf)
|
|
_psf->AddRef();
|
|
return OrderList_LoadFromStream(pstm, phdpa, _psf);
|
|
}
|
|
|
|
// IOrderList2::SaveToStream
|
|
STDMETHODIMP COrderList::SaveToStream(IStream* pstm, HDPA hdpa)
|
|
{
|
|
return OrderList_SaveToStream(pstm, hdpa, _psf);
|
|
}
|