/*
 * propstg.c - Property storage ADT 
 */


#include "priv.h"
#include "propstg.h"

#ifndef UNIX
// SafeGetItemObject
//
// Since the GetItemObject member of IShellView was added late in the game
// during Win95 development we have found at least one example (rnaui.dll)
// of an application that built an IShellView with a NULL member for
// GetItemObject.  Fearing more applications that may have the same
// problem, this wrapper function was added to catch bad apps like rnaui.
// Thus, we check here for NULL before calling the member.
//
STDAPI SafeGetItemObject(LPSHELLVIEW psv, UINT uItem, REFIID riid, LPVOID *ppv)
{
#ifdef __cplusplus
#error THIS_MUST_STAY_C
    // read the comment above
#endif
    if (!psv->lpVtbl->GetItemObject)
        return E_FAIL;

    return (HRESULT)(psv->lpVtbl->GetItemObject(psv, uItem, riid, ppv));
}
#endif


// This structure is a dictionary element. It maps a name to a propid.  

typedef struct
{
    PROPID  propid;
    WCHAR   wszName[MAX_PATH];
} DICTEL, * PDICTEL;


// This structure is a propvariant element.
typedef struct
{
    PROPVARIANT propvar;
    DWORD       dwFlags;        // PEF_*
} PROPEL, * PPROPEL;

// Flags for PROPEL structure
#define PEF_VALID           0x00000001
#define PEF_DIRTY           0x00000002

// This structure is the ADT for property storage

typedef struct
{
    DWORD   cbSize;
    DWORD   dwFlags;
    HDSA    hdsaProps;          // array of properties (indexed by propid) 
    HDPA    hdpaDict;           // dictionary of names mapped to propid 
    //  (each element is a DICTEL) 
    int     idsaLastValid;
} PROPSTG, * PPROPSTG;


// The first two entries in hdsaProps are reserved.  When we enumerate
// thru the list, we skip these entries.
#define PROPID_DICT     0
#define PROPID_CODEPAGE 1

#define IDSA_START      2
#define CDSA_RESERVED   2

BOOL IsValidHPROPSTG(HPROPSTG hstg)
{
    PPROPSTG pstg = (PPROPSTG)hstg;
    
    return (IS_VALID_WRITE_PTR(pstg, PROPSTG) &&
        SIZEOF(*pstg) == pstg->cbSize && 
        NULL != pstg->hdsaProps &&
        NULL != pstg->hdpaDict);
}

#ifdef DEBUG
BOOL IsValidPPROPSPEC(PROPSPEC * ppropspec)
{
    return (ppropspec &&
        PRSPEC_PROPID == ppropspec->ulKind ||
        (PRSPEC_LPWSTR == ppropspec->ulKind && 
        IS_VALID_STRING_PTRW(ppropspec->DUMMYUNION_MEMBER(lpwstr), -1)));
}
#endif

STDAPI PropStg_Create(OUT HPROPSTG * phstg, IN  DWORD dwFlags)
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_WRITE_PTR(phstg, HPROPSTG)))
    {
        PPROPSTG pstg = (PPROPSTG)LocalAlloc(LPTR, SIZEOF(*pstg));
        
        hres = STG_E_INSUFFICIENTMEMORY;       // assume error 
        
        if (pstg) 
        {
            pstg->cbSize = SIZEOF(*pstg);
            pstg->dwFlags = dwFlags;
            pstg->idsaLastValid = PROPID_CODEPAGE;
            
            pstg->hdsaProps = DSA_Create(SIZEOF(PROPEL), 8);
            pstg->hdpaDict = DPA_Create(8);
            
            if (pstg->hdsaProps && pstg->hdpaDict)
            {
                // The first two propids are reserved, so insert
                // placeholders.
                PROPEL propel;
                
                propel.propvar.vt = VT_EMPTY;
                propel.dwFlags = 0;
                
                DSA_SetItem(pstg->hdsaProps, PROPID_DICT, &propel);
                DSA_SetItem(pstg->hdsaProps, PROPID_CODEPAGE, &propel);
                
                hres = S_OK;
            }
            else
            {
                // Clean up because something failed 
                if (pstg->hdsaProps)
                {
                    DSA_Destroy(pstg->hdsaProps);
                    pstg->hdsaProps = NULL;
                }
                
                if (pstg->hdpaDict)
                {
                    DPA_Destroy(pstg->hdpaDict);
                    pstg->hdpaDict = NULL;
                }
                
                LocalFree(pstg);
                pstg = NULL;
            }
        }
        
        *phstg = (HPROPSTG)pstg;
        
        // Validate return values
        ASSERT((SUCCEEDED(hres) && 
            IS_VALID_WRITE_PTR(*phstg, PPROPSTG)) ||
            (FAILED(hres) && NULL == *phstg));
    }
    
    return hres;
}


STDAPI PropStg_Destroy(HPROPSTG hstg)
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_HANDLE(hstg, PROPSTG)))
    {
        PPROPSTG pstg = (PPROPSTG)hstg;
        
        if (pstg->hdsaProps)
        {
            int cdsa = DSA_GetItemCount(pstg->hdsaProps) - CDSA_RESERVED;
            
            // The first two elements are not cleared, because they
            // are just place-holders.
            
            if (0 < cdsa)
            {
                PPROPEL ppropel = DSA_GetItemPtr(pstg->hdsaProps, IDSA_START);
                
                ASSERT(ppropel);
                
                while (0 < cdsa--)
                {
                    PropVariantClear(&ppropel->propvar);
                    ppropel++;
                }
            }
            
            DSA_Destroy(pstg->hdsaProps);
            pstg->hdsaProps = NULL;
        }
        
        if (pstg->hdpaDict)
        {
            int i, cel = DPA_GetPtrCount(pstg->hdpaDict);
            for (i = 0; i < cel; i++)
            {
                LocalFree(DPA_FastGetPtr(pstg->hdpaDict, i));
            }
            DPA_Destroy(pstg->hdpaDict);
            pstg->hdpaDict = NULL;
        }
        
        LocalFree(pstg);
        pstg = NULL;
        
        hres = S_OK;
    }
    
    return hres;
}


/*----------------------------------------------------------
Purpose: Compare names

Returns: standard -1, 0, 1
Cond:    --
*/
int CALLBACK PropStg_Compare(IN LPVOID pv1, IN LPVOID pv2, IN LPARAM lParam)
{
    LPCWSTR psz1 = pv1;
    LPCWSTR psz2 = pv2;
    
    // Case insensitive 
    return StrCmpW(psz1, psz2);
}


/*----------------------------------------------------------
Purpose: Returns TRUE if the property exists in this storage.
         If it does exist, the propid is returned.

Returns: TRUE 
         FALSE 

Cond:    --
*/
BOOL PropStg_PropertyExists(IN  PPROPSTG   pstg,
                            IN  const PROPSPEC * ppropspec,
                            OUT PROPID *   ppropid)
{
    BOOL bRet;
    PPROPEL ppropel;
    HDSA hdsaProps;
    
    ASSERT(pstg);
    ASSERT(ppropspec);
    ASSERT(ppropid);
    
    hdsaProps = pstg->hdsaProps;
    
    switch (ppropspec->ulKind)
    {
    case PRSPEC_PROPID:
        *ppropid = ppropspec->DUMMYUNION_MEMBER(propid);
        
        bRet = (*ppropid < (PROPID)DSA_GetItemCount(hdsaProps));
        if (bRet)
        {
            ppropel = DSA_GetItemPtr(hdsaProps, *ppropid);
            bRet = (ppropel && IsFlagSet(ppropel->dwFlags, PEF_VALID));
        }
        break;
        
    case PRSPEC_LPWSTR:
        // Key off whether the name exists 
        *ppropid = DPA_Search(pstg->hdpaDict, ppropspec->DUMMYUNION_MEMBER(lpwstr), 0, PropStg_Compare, 0, DPAS_SORTED);
        
#ifdef DEBUG
        // Sanity check that the property actually exists
        ppropel = DSA_GetItemPtr(hdsaProps, *ppropid);
        ASSERT(-1 == *ppropid ||
            (ppropel && IsFlagSet(ppropel->dwFlags, PEF_VALID)));
#endif
        
        bRet = (-1 != *ppropid);
        break;
        
    default:
        bRet = FALSE;
        break;
    }
    
    // Propid values 0 and 1 are reserved, as are values >= 0x80000000 
    if (bRet && (0 == *ppropid || 1 == *ppropid || 0x80000000 <= *ppropid))
        bRet = FALSE;
    
    return bRet;
}


/*----------------------------------------------------------
Purpose: Create a new propid and assign the given name to
         it.  

         The propid is an index into hdsaProps.

Returns: S_OK
         STG_E_INSUFFICIENTMEMORY

Cond:    --
*/
HRESULT PropStg_NewPropid(IN  PPROPSTG pstg,
                          IN  LPCWSTR  pwsz,
                          IN  PROPID   propidFirst,
                          OUT PROPID * ppropid)           OPTIONAL
{
    HRESULT hres = STG_E_INVALIDPOINTER;        // assume error
    DICTEL * pdictel;
    PROPID propid = (PROPID)-1;
    HDPA hdpa;
    
    ASSERT(pstg);
    ASSERT(ppropid);
    
    if (EVAL(IS_VALID_STRING_PTRW(pwsz, -1)))
    {
        hres = STG_E_INSUFFICIENTMEMORY;            // assume error
        
        hdpa = pstg->hdpaDict;
        
        // The name shouldn't be in the list yet 
        ASSERT(-1 == DPA_Search(hdpa, (LPVOID)pwsz, 0, PropStg_Compare, 0, DPAS_SORTED));
        
        pdictel = LocalAlloc(LPTR, SIZEOF(*pdictel));
        if (pdictel)
        {
            // Determine the propid for this 
            PROPID propidNew = max(propidFirst, (PROPID)pstg->idsaLastValid + 1);
            
            pdictel->propid = propidNew;
            
            StrCpyNW(pdictel->wszName, pwsz, ARRAYSIZE(pdictel->wszName));
            
            if (-1 != DPA_AppendPtr(hdpa, pdictel))
            {
                // Sort it by name
                DPA_Sort(hdpa, PropStg_Compare, 0);
                hres = S_OK;
                propid = propidNew;
            }
        }
    }
    
    *ppropid = propid;
    
    return hres;
}


/*----------------------------------------------------------
Purpose: Read a set of properties given their propids.  If the propid 
         doesn't exist in this property storage, then set the
         value type to VT_EMPTY but return success; unless
         all the properties in rgpropspec don't exist, in which
         case also return S_FALSE.  

Returns: S_OK
         S_FALSE
         STG_E_INVALIDPARAMETER 
         STG_E_INSUFFICIENTMEMORY 

Cond:    --
*/
STDAPI PropStg_ReadMultiple(IN HPROPSTG      hstg,
                            IN ULONG         cpspec,
                            IN const PROPSPEC * rgpropspec,
                            IN PROPVARIANT * rgpropvar)
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_HANDLE(hstg, PROPSTG)) &&
        EVAL(IS_VALID_READ_BUFFER(rgpropspec, PROPSPEC, cpspec)) &&
        EVAL(IS_VALID_READ_BUFFER(rgpropvar, PROPVARIANT, cpspec)))
    {
        PPROPSTG pstg = (PPROPSTG)hstg;
        ULONG cpspecSav = cpspec;
        const PROPSPEC * ppropspec = rgpropspec;
        PROPVARIANT * ppropvar = rgpropvar;
        int idsa;
        BOOL bPropertyExists;
        ULONG cpspecIllegal = 0;
        
        hres = S_OK;        // assume success
        
        if (0 < cpspec)
        {
            // Read the list of property specs 
            while (0 < cpspec--)
            {
                bPropertyExists = PropStg_PropertyExists(pstg, ppropspec, (LPDWORD)&idsa);
                
                // Does this property exist? 
                if ( !bPropertyExists )
                {
                    // No
                    ppropvar->vt = VT_ILLEGAL;
                    cpspecIllegal++;
                }
                else
                {
                    // Yes; is the element a valid property? 
                    PPROPEL ppropel = DSA_GetItemPtr(pstg->hdsaProps, idsa);
                    
                    ASSERT(ppropel);
                    
                    if (IsFlagSet(ppropel->dwFlags, PEF_VALID))
                    {
                        // Yes; copy the variant value
                        hres = PropVariantCopy(ppropvar, &ppropel->propvar);
                    }
                    else
                    {
                        // No
                        ppropvar->vt = VT_ILLEGAL;
                        cpspecIllegal++;
                    }
                }
                
                ppropspec++;
                ppropvar++;
                
                //  Bail out of loop if something failed 
                if (FAILED(hres))
                    break;
            }
            
            // Are all the property specs illegal? 
            if (cpspecIllegal == cpspecSav)
            {
                hres = S_FALSE;     // yes
            }
            
            // Did anything fail above?
            if (FAILED(hres))
            {
                // Yes; clean up -- no properties will be retrieved 
                FreePropVariantArray(cpspecSav, rgpropvar);
            }
        }
    }
    
    return hres;
}


/*----------------------------------------------------------
Purpose: Add a set of property values given their propids.  
         If the propid doesn't exist in this property storage, 
         then add the propid as a legal ID and set the value. 

         On error, some properties may or may not have been 
         written.

         If pfn is non-NULL, this callback will get called
         to optionally "massage" the propvariant value or to
         validate it.  The rules for the callback are:

            1)  It can change the value directly if it is not
                allocated

            2)  If the value is allocated, the callback must 
                replace the pointer with a newly allocated 
                buffer that it allocates.  It must not try
                to free the value coming in, since it doesn't
                know how it was allocated.  It must also use
                CoTaskMemAlloc to allocate its buffer. 

            3)  If the callback returns an error, this function
                will stop writing properties and return that
                error.

            4)  If the callback returns S_FALSE, this function
                will not write that particular property and 
                continue on to the next property.  The function
                then returns S_FALSE once it is finished.

Returns: S_OK
         S_FALSE
         STG_E_INVALIDPARAMETER 
         STG_E_INSUFFICIENTMEMORY 

Cond:    --
*/
STDAPI PropStg_WriteMultipleEx(IN HPROPSTG          hstg,
                               IN ULONG             cpspec,
                               IN const PROPSPEC *  rgpropspec,
                               IN const PROPVARIANT * rgpropvar,
                               IN PROPID            propidFirst,   OPTIONAL
                               IN PFNPROPVARMASSAGE pfn,           OPTIONAL
                               IN LPARAM            lParam)        OPTIONAL
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_HANDLE(hstg, PROPSTG)) &&
        EVAL(IS_VALID_READ_BUFFER(rgpropspec, PROPSPEC, cpspec)) &&
        EVAL(IS_VALID_READ_BUFFER(rgpropvar, PROPVARIANT, cpspec)))
    {
        PPROPSTG pstg = (PPROPSTG)hstg;
        const PROPSPEC * ppropspec = rgpropspec;
        const PROPVARIANT * ppropvar = rgpropvar;
        int idsa;
        PROPEL propel;
        BOOL bPropertyExists;
        BOOL bSkippedProperty = FALSE;
        
        if (0 == cpspec)
        {
            hres = S_OK;
        }
        else
        {
            // Write the list of property specs 
            while (0 < cpspec--)
            {
                bPropertyExists = PropStg_PropertyExists(pstg, ppropspec, (LPDWORD)&idsa);
                
                hres = S_OK;
                
                // If this is an illegal variant type and yet a valid 
                // property, then return an error.  Otherwise, ignore it
                // and move on.
                if (VT_ILLEGAL == ppropvar->vt)
                {
                    if (bPropertyExists)
                        hres = STG_E_INVALIDPARAMETER;
                    else
                        goto NextDude;
                }
                
                if (SUCCEEDED(hres))
                {
                    // Add the property.  If it doesn't exist, add it.
                    
                    // Is this a propid or a name? 
                    switch (ppropspec->ulKind)
                    {
                    case PRSPEC_PROPID:
                        idsa = ppropspec->DUMMYUNION_MEMBER(propid);
                        break;
                        
                    case PRSPEC_LPWSTR:
                        if ( !bPropertyExists )
                        {
                            hres = PropStg_NewPropid(pstg, ppropspec->DUMMYUNION_MEMBER(lpwstr), 
                                propidFirst, (LPDWORD)&idsa);
                        }
                        break;
                        
                    default:
                        hres = STG_E_INVALIDNAME;
                        break;
                    }
                    
                    if (SUCCEEDED(hres))
                    {
                        PROPVARIANT propvarT;
                        
                        ASSERT(S_OK == hres);   // we're assuming this on entry 
                        
                        // Save a copy of the original in case the
                        // callback changes it.
                        CopyMemory(&propvarT, ppropvar, SIZEOF(propvarT));
                        
                        // How did the callback like it? 
                        if (pfn)
                            hres = pfn(idsa, ppropvar, lParam);
                        
                        if (S_OK == hres)
                        {
                            // Fine; make a copy of the (possibly changed)
                            // propvariant value
                            hres = PropVariantCopy(&propel.propvar, ppropvar);
                            if (SUCCEEDED(hres))
                            {
                                propel.dwFlags = PEF_VALID | PEF_DIRTY;
                                
                                hres = (DSA_SetItem(pstg->hdsaProps, idsa, &propel) ? S_OK : STG_E_INSUFFICIENTMEMORY);
                                
                                if (SUCCEEDED(hres) && idsa > pstg->idsaLastValid)
                                {
                                    pstg->idsaLastValid = idsa;
                                }
                            }
                        }
                        else if (S_FALSE == hres)
                        {
                            bSkippedProperty = TRUE;
                        }
                        
                        // Restore the propvariant value to its original
                        // value. But first, did the callback allocate a
                        // new buffer? 
                        if (propvarT.DUMMYUNION_MEMBER(pszVal) != ppropvar->DUMMYUNION_MEMBER(pszVal))
                        {
                            // Yes; clear it (this function is safe for
                            // non-allocated values too).
                            PropVariantClear((PROPVARIANT *)ppropvar);
                        }
                        
                        // Restore
                        CopyMemory((PROPVARIANT *)ppropvar, &propvarT, SIZEOF(*ppropvar));
                        hres = S_OK;
                    }
                }
                
NextDude:
                ppropspec++;
                ppropvar++;
                
                //  Bail out of loop if something failed 
                if (FAILED(hres))
                    break;
            }
            
            if (bSkippedProperty)
                hres = S_FALSE;
            }
        }
        
        return hres;
    }
    

/*
Purpose: Add a set of property values given their propids.  
         If the propid doesn't exist in this property storage, 
         then add the propid as a legal ID and set the value. 

         On error, some properties may or may not have been 
         written.

Returns: S_OK
         STG_E_INVALIDPARAMETER 
         STG_E_INSUFFICIENTMEMORY 

*/
STDAPI PropStg_WriteMultiple(IN HPROPSTG      hstg,
                             IN ULONG         cpspec,
                             IN const PROPSPEC * rgpropspec,
                             IN const PROPVARIANT * rgpropvar,
                             IN PROPID        propidFirst)      OPTIONAL
{
    return PropStg_WriteMultipleEx(hstg, cpspec, rgpropspec, rgpropvar,
        propidFirst, NULL, 0);
}

STDAPI PropStg_DeleteMultiple(IN HPROPSTG      hstg,
                              IN ULONG         cpspec,
                              IN const PROPSPEC * rgpropspec)
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_HANDLE(hstg, PROPSTG)) &&
        EVAL(IS_VALID_READ_BUFFER(rgpropspec, PROPSPEC, cpspec)))
    {
        PPROPSTG pstg = (PPROPSTG)hstg;
        const PROPSPEC * ppropspec = rgpropspec;
        HDSA hdsaProps = pstg->hdsaProps;
        PPROPEL ppropel;
        int idsa;
        int cdsa;
        
        hres = S_OK;
        
        if (0 < cpspec)
        {
            BOOL bDeletedLastValid = FALSE;
            
            // Delete the list of property specs
            while (0 < cpspec--)
            {
                if (PropStg_PropertyExists(pstg, ppropspec, (LPDWORD)&idsa))
                {
                    // Delete the property.  Zero out the existing
                    // propel.  Don't call DSA_DeleteItem, otherwise
                    // we'll move the positions of any remaining
                    // properties following this one, thus changing their
                    // propids.
                    ppropel = DSA_GetItemPtr(hdsaProps, idsa);
                    ASSERT(ppropel);
                    
                    PropVariantClear(&ppropel->propvar);
                    ppropel->dwFlags = 0;
                    
                    // Our idsaLastValid is messed up if we hit this
                    // assert
                    ASSERT(idsa <= pstg->idsaLastValid);
                    
                    if (idsa == pstg->idsaLastValid)
                        bDeletedLastValid = TRUE;
                    
                    // Delete the names associated with the property 
                    // FEATURE (scotth): implement this
                }
                
                ppropspec++;
            }
            
            // Did we delete the property that was marked as the terminating
            // valid property in the list? 
            if (bDeletedLastValid)
            {
                // Yes; go back and search for the new terminating index 
                ppropel = DSA_GetItemPtr(hdsaProps, pstg->idsaLastValid);
                cdsa = pstg->idsaLastValid + 1 - CDSA_RESERVED;
                ASSERT(ppropel);
                
                while (0 < cdsa--)
                {
                    if (IsFlagSet(ppropel->dwFlags, PEF_VALID))
                    {
                        pstg->idsaLastValid = cdsa - 1;
                        break;
                    }
                    ppropel--;
                }
                
                if (0 == cdsa)
                    pstg->idsaLastValid = PROPID_CODEPAGE;
            }
            
            // Since we didn't delete any items from hdsaProps (we freed 
            // the variant value and zeroed it out), this structure 
            // may have a bunch of unused elements at the end.  
            // Compact now if necessary.
            
            // Do we have a bunch of trailing, empty elements? 
            cdsa = DSA_GetItemCount(hdsaProps);
            
            if (cdsa > pstg->idsaLastValid + 1)
            {
                // Yes; compact.  Start from the end and go backwards
                // so DSA_DeleteItem doesn't have to move memory blocks.
                for (idsa = cdsa-1; idsa > pstg->idsaLastValid; idsa--)
                {
#ifdef DEBUG
                    ppropel = DSA_GetItemPtr(hdsaProps, idsa);
                    ASSERT(IsFlagClear(ppropel->dwFlags, PEF_VALID));
#endif
                    DSA_DeleteItem(hdsaProps, idsa);
                }
            }
        }
    }
    return hres;
}


/*----------------------------------------------------------
Purpose: Marks the specified properties dirty or undirty, depending
         on the value of bDirty.  
         
Returns: S_OK
         STG_E_INVALIDPARAMETER 
         STG_E_INSUFFICIENTMEMORY 

Cond:    --
*/
STDAPI PropStg_DirtyMultiple(IN HPROPSTG    hstg,
                             IN ULONG       cpspec,
                             IN const PROPSPEC * rgpropspec,
                             IN BOOL        bDirty)
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_HANDLE(hstg, PROPSTG)) &&
        EVAL(IS_VALID_READ_BUFFER(rgpropspec, PROPSPEC, cpspec)))
    {
        PPROPSTG pstg = (PPROPSTG)hstg;
        const PROPSPEC * ppropspec = rgpropspec;
        HDSA hdsaProps = pstg->hdsaProps;
        PPROPEL ppropel;
        int idsa;
        
        hres = S_OK;
        
        if (0 < cpspec)
        {
            // Mark the list of property specs
            while (0 < cpspec--)
            {
                // Does it exist?
                if (PropStg_PropertyExists(pstg, ppropspec, (LPDWORD)&idsa))
                {
                    // Yes; mark it
                    ppropel = DSA_GetItemPtr(hdsaProps, idsa);
                    ASSERT(ppropel);
                    
                    if (bDirty)
                    {
                        SetFlag(ppropel->dwFlags, PEF_DIRTY);
                    }
                    else
                    {
                        ClearFlag(ppropel->dwFlags, PEF_DIRTY);
                    }
                }
                
                ppropspec++;
            }
        }
    }
    
    return hres;
}


/*----------------------------------------------------------
Purpose: Marks or unmarks all the property values.
         
Returns: S_OK
         STG_E_INVALIDPARAMETER 
         STG_E_INSUFFICIENTMEMORY 

Cond:    --
*/
STDAPI PropStg_DirtyAll(IN HPROPSTG hstg,
                        IN BOOL     bDirty)
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_HANDLE(hstg, PROPSTG)))
    {
        PPROPSTG pstg = (PPROPSTG)hstg;
        int cdsa = pstg->idsaLastValid + 1 - CDSA_RESERVED;
        
        hres = S_OK;
        
        if (0 < cdsa)
        {
            PPROPEL ppropel = DSA_GetItemPtr(pstg->hdsaProps, IDSA_START);
            
            ASSERT(ppropel);
            
            while (0 < cdsa--)
            {
                if (bDirty)
                    SetFlag(ppropel->dwFlags, PEF_DIRTY);
                else
                    ClearFlag(ppropel->dwFlags, PEF_DIRTY);
                ppropel++;
            }
        }
    }
    
    return hres;
}


/*----------------------------------------------------------
Purpose: Returns S_OK if at least one property value is dirty
         in the storage.  Otherwise, this function returns 
         S_FALSE.
         
Returns: S_OK if it is dirty
         S_FALSE if not
         STG_E_INVALIDPARAMETER 
         STG_E_INSUFFICIENTMEMORY 

Cond:    --
*/
STDAPI PropStg_IsDirty(HPROPSTG hstg)
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_HANDLE(hstg, PROPSTG)))
    {
        PPROPSTG pstg = (PPROPSTG)hstg;
        int cdsa = pstg->idsaLastValid + 1 - CDSA_RESERVED;
        
        hres = S_FALSE;
        
        if (0 < cdsa)
        {
            PPROPEL ppropel = DSA_GetItemPtr(pstg->hdsaProps, IDSA_START);
            
            ASSERT(ppropel);
            
            while (0 < cdsa--)
            {
                if (IsFlagSet(ppropel->dwFlags, PEF_DIRTY))
                {
                    hres = S_OK;
                    break;
                }
                ppropel++;
            }
        }
    }
    
    return hres;
}


/*----------------------------------------------------------
Purpose: Enumerates thru the list of properties.
         
Returns: S_OK
         STG_E_INVALIDPARAMETER 
         STG_E_INSUFFICIENTMEMORY 

Cond:    --
*/
STDAPI PropStg_Enum(IN HPROPSTG       hstg,
                    IN DWORD          dwFlags,      // One of PSTGEF_ 
                    IN PFNPROPSTGENUM pfnEnum,
                    IN LPARAM         lParam)       OPTIONAL
{
    HRESULT hres = STG_E_INVALIDPARAMETER;
    
    if (EVAL(IS_VALID_HANDLE(hstg, PROPSTG)) &&
        EVAL(IS_VALID_CODE_PTR(pfnEnum, PFNPROPSTGENUM)))
    {
        PPROPSTG pstg = (PPROPSTG)hstg;
        int cdsa = pstg->idsaLastValid + 1 - CDSA_RESERVED;
        DWORD dwFlagsPEF = 0;
        
        hres = S_OK;
        
        // Set the filter flags
        if (dwFlags & PSTGEF_DIRTY)
            SetFlag(dwFlagsPEF, PEF_DIRTY);
        
        if (0 < cdsa)
        {
            PPROPEL ppropel = DSA_GetItemPtr(pstg->hdsaProps, IDSA_START);
            int idsa = IDSA_START;
            
            ASSERT(ppropel);
            
            while (0 < cdsa--)
            {
                // Does it pass thru filter? 
                if (IsFlagSet(ppropel->dwFlags, PEF_VALID) &&
                    (0 == dwFlagsPEF || (dwFlagsPEF & ppropel->dwFlags)))
                {
                    // Yes, call callback
                    HRESULT hresT = pfnEnum(idsa, &ppropel->propvar, lParam);
                    if (S_OK != hresT)
                    {
                        if (FAILED(hresT))
                            hres = hresT;
                        break;      // stop enumeration
                    }
                }
                ppropel++;
                idsa++;
            }
        }
    }
    
    return hres;
}


#ifdef DEBUG

HRESULT CALLBACK PropStg_DumpVar(IN PROPID        propid,
                                 IN PROPVARIANT * ppropvar,
                                 IN LPARAM        lParam)
{
    TCHAR sz[MAX_PATH];
    PPROPEL ppropel = (PPROPEL)ppropvar;        // we're cheating here
    
    if (IsFlagSet(ppropel->dwFlags, PEF_DIRTY))
        wnsprintf(sz, ARRAYSIZE(sz), TEXT("    *id:%#lx\t%s"), propid, Dbg_GetVTName(ppropvar->vt));
    else
        wnsprintf(sz, ARRAYSIZE(sz), TEXT("     id:%#lx\t%s"), propid, Dbg_GetVTName(ppropvar->vt));
    
    
    switch (ppropvar->vt)
    {
    case VT_EMPTY:
    case VT_NULL:
    case VT_ILLEGAL:
        TraceMsg(TF_ALWAYS, "     %s", sz);
        break;
        
    case VT_I2:
    case VT_I4:
        TraceMsg(TF_ALWAYS, "     %s\t%d", sz, ppropvar->DUMMYUNION_MEMBER(lVal));
        break;
        
    case VT_UI1:
        TraceMsg(TF_ALWAYS, "     %s\t%#02x '%c'", sz, ppropvar->DUMMYUNION_MEMBER(bVal), ppropvar->DUMMYUNION_MEMBER(bVal));
        break;
    case VT_UI2:
        TraceMsg(TF_ALWAYS, "     %s\t%#04x", sz, ppropvar->DUMMYUNION_MEMBER(uiVal));
        break;
    case VT_UI4:
        TraceMsg(TF_ALWAYS, "     %s\t%#08x", sz, ppropvar->DUMMYUNION_MEMBER(ulVal));
        break;
        
    case VT_LPSTR:
        TraceMsg(TF_ALWAYS, "     %s\t\"%S\"", sz, Dbg_SafeStrA(ppropvar->DUMMYUNION_MEMBER(pszVal)));
        break;
    case VT_LPWSTR:
        TraceMsg(TF_ALWAYS, "     %s\t\"%ls\"", sz, Dbg_SafeStrW(ppropvar->DUMMYUNION_MEMBER(pwszVal)));
        break;
        
    default:
#if defined(_WIN64)
        TraceMsg(TF_ALWAYS, "     %s\t0x%p", sz, (DWORD_PTR)ppropvar->DUMMYUNION_MEMBER(pszVal)); 
#else
        TraceMsg(TF_ALWAYS, "     s\t%#08lx", sz, (DWORD)ppropvar->DUMMYUNION_MEMBER(pszVal));
#endif
        
        break;
    }
    return S_OK;
}

STDAPI PropStg_Dump(IN HPROPSTG       hstg,
                    IN DWORD          dwFlags)      // One of PSTGDF_ 
{
    TraceMsg(TF_ALWAYS, "  Property storage 0x%08lx = {", hstg);
    
    PropStg_Enum(hstg, 0, PropStg_DumpVar, 0);
    
    TraceMsg(TF_ALWAYS, "  }");
    
    return NOERROR;
}

#endif


