#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);
}
