windows-nt/Source/XPSP1/NT/shell/shell32/storage.cpp
2020-09-26 16:20:57 +08:00

1471 lines
37 KiB
C++

#include "shellprv.h"
#pragma hdrstop
#include "debug.h"
#include "stgutil.h"
#include "ids.h"
#include "tngen\ctngen.h"
#include "tlist.h"
#include "thumbutil.h"
void SHGetThumbnailSizeForThumbsDB(SIZE *psize);
STDAPI CThumbStore_CreateInstance(IUnknown* punkOuter, REFIID riid, void **ppv);
class CThumbStore : public IShellImageStore,
public IPersistFolder,
public IPersistFile,
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass< CThumbStore,&CLSID_ShellThumbnailDiskCache >
{
struct CATALOG_ENTRY
{
DWORD cbSize;
DWORD dwIndex;
FILETIME ftTimeStamp;
WCHAR szName[1];
};
struct CATALOG_HEADER
{
WORD cbSize;
WORD wVersion;
DWORD dwEntryCount;
SIZE szThumbnailExtent;
};
public:
BEGIN_COM_MAP(CThumbStore)
COM_INTERFACE_ENTRY_IID(IID_IShellImageStore,IShellImageStore)
COM_INTERFACE_ENTRY(IPersistFolder)
COM_INTERFACE_ENTRY(IPersistFile)
END_COM_MAP()
DECLARE_NOT_AGGREGATABLE(CThumbStore)
CThumbStore();
~CThumbStore();
// IPersist
STDMETHOD(GetClassID)(CLSID *pClassID);
// IPersistFolder
STDMETHOD(Initialize)(LPCITEMIDLIST pidl);
// IPersistFile
STDMETHOD (IsDirty)(void);
STDMETHOD (Load)(LPCWSTR pszFileName, DWORD dwMode);
STDMETHOD (Save)(LPCWSTR pszFileName, BOOL fRemember);
STDMETHOD (SaveCompleted)(LPCWSTR pszFileName);
STDMETHOD (GetCurFile)(LPWSTR *ppszFileName);
// IImageCache
STDMETHOD (Open)(DWORD dwMode, DWORD *pdwLock);
STDMETHOD (Create)(DWORD dwMode, DWORD *pdwLock);
STDMETHOD (Close)(DWORD const *pdwLock);
STDMETHOD (Commit)(DWORD const *pdwLock);
STDMETHOD (ReleaseLock)(DWORD const *pdwLock);
STDMETHOD (IsLocked)(THIS);
STDMETHOD (GetMode)(DWORD *pdwMode);
STDMETHOD (GetCapabilities)(DWORD *pdwCapMask);
STDMETHOD (AddEntry)(LPCWSTR pszName, const FILETIME *pftTimeStamp, DWORD dwMode, HBITMAP hImage);
STDMETHOD (GetEntry)(LPCWSTR pszName, DWORD dwMode, HBITMAP *phImage);
STDMETHOD (DeleteEntry)(LPCWSTR pszName);
STDMETHOD (IsEntryInStore)(LPCWSTR pszName, FILETIME *pftTimeStamp);
STDMETHOD (Enum)(IEnumShellImageStore ** ppEnum);
protected:
friend class CEnumThumbStore;
HRESULT LoadCatalog(void);
HRESULT SaveCatalog(void);
HRESULT FindStreamID(LPCWSTR pszName, DWORD *pdwStream, CATALOG_ENTRY **ppEntry);
HRESULT GetEntryStream(DWORD dwStream, DWORD dwMode, IStream **ppStream);
DWORD GetAccessMode(DWORD dwMode, BOOL fStream);
DWORD AcquireLock(void);
void ReleaseLock(DWORD dwLock);
BOOL InitCodec(void);
HRESULT PrepImage(HBITMAP *phBmp, SIZE * prgSize, void ** ppBits);
BOOL DecompressImage(void * pvInBuffer, ULONG ulBufferSize, HBITMAP *phBmp);
BOOL CompressImage(HBITMAP hBmp, void ** ppvOutBuffer, ULONG * plBufSize);
HRESULT WriteImage(IStream *pStream, HBITMAP hBmp);
HRESULT ReadImage(IStream *pStream, HBITMAP *phBmp);
BOOL _MatchNodeName(CATALOG_ENTRY *pNode, LPCWSTR pszName);
HRESULT _InitFromPath(LPCTSTR pszPath, DWORD dwMode);
void _SetAttribs(BOOL bForce);
CATALOG_HEADER m_rgHeader;
CList<CATALOG_ENTRY> m_rgCatalog;
IStorage *_pStorageThumb;
DWORD _dwModeStorage;
DWORD m_dwModeAllow;
WCHAR m_szPath[MAX_PATH];
DWORD m_dwMaxIndex;
DWORD m_dwCatalogChange;
// Crit section used to protect the internals
CRITICAL_SECTION m_csInternals;
// needed for this object to be free-threaded... so that
// we can query the catalog from the main thread whilst icons are
// being read and written from the main thread.
CRITICAL_SECTION m_csLock;
DWORD m_dwLock;
int m_fLocked;
CThumbnailFCNContainer * m_pJPEGCodec;
};
HRESULT CEnumThumbStore_Create(CThumbStore * pThis, IEnumShellImageStore ** ppEnum);
class CEnumThumbStore : public IEnumShellImageStore,
public CComObjectRoot
{
public:
BEGIN_COM_MAP(CEnumThumbStore)
COM_INTERFACE_ENTRY_IID(IID_IEnumShellImageStore,IEnumShellImageStore)
END_COM_MAP()
CEnumThumbStore();
~CEnumThumbStore();
STDMETHOD (Reset)(void);
STDMETHOD (Next)(ULONG celt, PENUMSHELLIMAGESTOREDATA *prgElt, ULONG *pceltFetched);
STDMETHOD (Skip)(ULONG celt);
STDMETHOD (Clone)(IEnumShellImageStore ** pEnum);
protected:
friend HRESULT CEnumThumbStore_Create(CThumbStore *pThis, IEnumShellImageStore **ppEnum);
CThumbStore * m_pStore;
CLISTPOS m_pPos;
DWORD m_dwCatalogChange;
};
#define THUMB_FILENAME L"Thumbs.db"
#define CATALOG_STREAM L"Catalog"
#define CATALOG_VERSION 0x0006
#define CATALOG_VERSION_XPGOLD 0x0005
#define STREAMFLAGS_JPEG 0x0001
#define STREAMFLAGS_DIB 0x0002
struct STREAM_HEADER
{
DWORD cbSize;
DWORD dwFlags;
ULONG ulSize;
};
void GenerateStreamName(LPWSTR pszBuffer, DWORD cbSize, DWORD dwNumber);
HRESULT ReadImage(IStream *pStream, HBITMAP *phImage);
HRESULT WriteImage(IStream *pStream, HBITMAP hImage);
BITMAPINFO *BitmapToDIB(HBITMAP hBmp);
CThumbStore::CThumbStore()
{
m_szPath[0] = 0;
m_rgHeader.dwEntryCount = 0;
m_rgHeader.wVersion = CATALOG_VERSION;
m_rgHeader.cbSize = sizeof(m_rgHeader);
SHGetThumbnailSizeForThumbsDB(&m_rgHeader.szThumbnailExtent);
m_dwMaxIndex = 0;
m_dwModeAllow = STGM_READWRITE;
// this counter is inc'd everytime the catalog changes so that we know when it
// must be committed and so enumerators can detect the list has changed...
m_dwCatalogChange = 0;
m_fLocked = 0;
InitializeCriticalSection(&m_csLock);
InitializeCriticalSection(&m_csInternals);
}
CThumbStore::~CThumbStore()
{
CLISTPOS pCur = m_rgCatalog.GetHeadPosition();
while (pCur != NULL)
{
CATALOG_ENTRY *pNode = m_rgCatalog.GetNext(pCur);
ASSERT(pNode != NULL);
LocalFree((void *) pNode);
}
m_rgCatalog.RemoveAll();
if (_pStorageThumb)
{
_pStorageThumb->Release();
}
if (m_pJPEGCodec)
{
delete m_pJPEGCodec;
}
// assume these are free, we are at ref count zero, no one should still be calling us...
DeleteCriticalSection(&m_csLock);
DeleteCriticalSection(&m_csInternals);
}
STDAPI CThumbStore_CreateInstance(IUnknown* punkOuter, REFIID riid, void **ppv)
{
return CComCreator< CComObject< CThumbStore > >::CreateInstance((void *)punkOuter, riid, (void **)ppv);
}
DWORD CThumbStore::AcquireLock(void)
{
EnterCriticalSection(&m_csLock);
// inc the lock (we use a counter because we may reenter this on the same thread)
m_fLocked++;
// Never return a lock signature of zero, because that means "not locked".
if (++m_dwLock == 0)
++m_dwLock;
return m_dwLock;
}
void CThumbStore::ReleaseLock(DWORD dwLock)
{
if (dwLock)
{
ASSERT(m_fLocked);
m_fLocked--;
LeaveCriticalSection(&m_csLock);
}
}
// the structure of the catalog is simple, it is a just a header stream
HRESULT CThumbStore::LoadCatalog()
{
HRESULT hr;
if (_pStorageThumb == NULL)
{
hr = E_UNEXPECTED;
}
else if (m_rgHeader.dwEntryCount != 0)
{
// it is already loaded....
hr = S_OK;
}
else
{
// open the catalog stream...
IStream *pCatalog;
hr = _pStorageThumb->OpenStream(CATALOG_STREAM, NULL, GetAccessMode(STGM_READ, TRUE), NULL, &pCatalog);
if (SUCCEEDED(hr))
{
EnterCriticalSection(&m_csInternals);
// now read in the catalog from the stream ...
hr = IStream_Read(pCatalog, &m_rgHeader, sizeof(m_rgHeader));
if (SUCCEEDED(hr))
{
SIZE szCurrentSize;
SHGetThumbnailSizeForThumbsDB(&szCurrentSize);
if ((m_rgHeader.cbSize != sizeof(m_rgHeader)) || (m_rgHeader.wVersion != CATALOG_VERSION) ||
(m_rgHeader.szThumbnailExtent.cx != szCurrentSize.cx) || (m_rgHeader.szThumbnailExtent.cy != szCurrentSize.cy))
{
if (m_rgHeader.wVersion == CATALOG_VERSION_XPGOLD)
{
hr = STG_E_DOCFILECORRUPT; // SECURITY: Many issues encrypting XPGOLD thumbnail databases, just delete it
_pStorageThumb->Release();
_pStorageThumb = NULL;
}
else
{
_SetAttribs(TRUE); // SECURITY: Old formats can't be encrypted
hr = STG_E_OLDFORMAT;
}
}
else
{
for (UINT iEntry = 0; (iEntry < m_rgHeader.dwEntryCount) && SUCCEEDED(hr); iEntry++)
{
DWORD cbSize;
hr = IStream_Read(pCatalog, &cbSize, sizeof(cbSize));
if (SUCCEEDED(hr))
{
ASSERT(cbSize <= sizeof(CATALOG_ENTRY) + sizeof(WCHAR) * MAX_PATH);
CATALOG_ENTRY *pEntry = (CATALOG_ENTRY *)LocalAlloc(LPTR, cbSize);
if (pEntry)
{
pEntry->cbSize = cbSize;
// read the rest with out the size on the front...
hr = IStream_Read(pCatalog, ((BYTE *)pEntry + sizeof(cbSize)), cbSize - sizeof(cbSize));
if (SUCCEEDED(hr))
{
CLISTPOS pCur = m_rgCatalog.AddTail(pEntry);
if (pCur)
{
if (m_dwMaxIndex < pEntry->dwIndex)
{
m_dwMaxIndex = pEntry->dwIndex;
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
else
{
hr = E_OUTOFMEMORY;
}
}
}
}
}
if (FAILED(hr))
{
// reset the catalog header...
m_rgHeader.wVersion = CATALOG_VERSION;
m_rgHeader.cbSize = sizeof(m_rgHeader);
SHGetThumbnailSizeForThumbsDB(&m_rgHeader.szThumbnailExtent);
m_rgHeader.dwEntryCount = 0;
}
m_dwCatalogChange = 0;
LeaveCriticalSection(&m_csInternals);
pCatalog->Release();
}
}
return hr;
}
HRESULT CThumbStore::SaveCatalog()
{
_pStorageThumb->DestroyElement(CATALOG_STREAM);
IStream *pCatalog;
HRESULT hr = _pStorageThumb->CreateStream(CATALOG_STREAM, GetAccessMode(STGM_WRITE, TRUE), NULL, NULL, &pCatalog);
if (SUCCEEDED(hr))
{
EnterCriticalSection(&m_csInternals);
// now write the catalog to the stream ...
hr = IStream_Write(pCatalog, &m_rgHeader, sizeof(m_rgHeader));
if (SUCCEEDED(hr))
{
CLISTPOS pCur = m_rgCatalog.GetHeadPosition();
while (pCur && SUCCEEDED(hr))
{
CATALOG_ENTRY *pEntry = m_rgCatalog.GetNext(pCur);
if (pEntry)
{
hr = IStream_Write(pCatalog, pEntry, pEntry->cbSize);
}
}
}
if (SUCCEEDED(hr))
m_dwCatalogChange = 0;
LeaveCriticalSection(&m_csInternals);
pCatalog->Release();
}
return hr;
}
void GenerateStreamName(LPWSTR pszBuffer, DWORD cbSize, DWORD dwNumber)
{
UINT cPos = 0;
while (dwNumber > 0)
{
DWORD dwRem = dwNumber % 10;
// based the fact that UNICODE chars 0-9 are the same as the ANSI chars 0 - 9
pszBuffer[cPos++] = (WCHAR)(dwRem + '0');
dwNumber /= 10;
}
pszBuffer[cPos] = 0;
}
// IPersist methods
STDMETHODIMP CThumbStore::GetClassID(CLSID *pClsid)
{
*pClsid = CLSID_ShellThumbnailDiskCache;
return S_OK;
}
// IPersistFolder
STDMETHODIMP CThumbStore::Initialize(LPCITEMIDLIST pidl)
{
WCHAR szPath[MAX_PATH];
HRESULT hr = SHGetPathFromIDList(pidl, szPath) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
if (PathAppend(szPath, THUMB_FILENAME))
hr = _InitFromPath(szPath, STGM_READWRITE);
else
hr = E_INVALIDARG;
}
return hr;
}
// IPersistFile
STDMETHODIMP CThumbStore::IsDirty(void)
{
return m_dwCatalogChange ? S_OK : S_FALSE;
}
HRESULT CThumbStore::_InitFromPath(LPCTSTR pszPath, DWORD dwMode)
{
if (PathIsRemovable(pszPath))
dwMode = STGM_READ;
m_dwModeAllow = dwMode;
StrCpyN(m_szPath, pszPath, ARRAYSIZE(m_szPath));
return S_OK;
}
STDMETHODIMP CThumbStore::Load(LPCWSTR pszFileName, DWORD dwMode)
{
TCHAR szPath[MAX_PATH];
PathCombine(szPath, pszFileName, THUMB_FILENAME);
return _InitFromPath(szPath, dwMode);
}
STDMETHODIMP CThumbStore::Save(LPCWSTR pszFileName, BOOL fRemember)
{
return E_NOTIMPL;
}
STDMETHODIMP CThumbStore::SaveCompleted(LPCWSTR pszFileName)
{
return E_NOTIMPL;
}
STDMETHODIMP CThumbStore::GetCurFile(LPWSTR *ppszFileName)
{
return SHStrDupW(m_szPath, ppszFileName);
}
// IShellImageStore methods
void CThumbStore::_SetAttribs(BOOL bForce)
{
// reduce spurious changenotifies by checking file attribs first
DWORD dwAttrib = GetFileAttributes(m_szPath);
if (bForce ||
((dwAttrib != 0xFFFFFFFF) &&
(dwAttrib & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)))
{
SetFileAttributes(m_szPath, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
WCHAR szStream[MAX_PATH + 13];
StrCpyNW(szStream, m_szPath, ARRAYSIZE(szStream));
StrCatW(szStream, TEXT(":encryptable"));
HANDLE hStream = CreateFile(szStream, GENERIC_WRITE, NULL, NULL, CREATE_NEW, NULL, NULL);
if (hStream != INVALID_HANDLE_VALUE)
{
CloseHandle(hStream);
}
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, m_szPath, NULL); // suppress the update dir
}
}
STDMETHODIMP CThumbStore::Open(DWORD dwMode, DWORD *pdwLock)
{
if (m_szPath[0] == 0)
{
return E_UNEXPECTED;
}
if ((m_dwModeAllow == STGM_READ) && (dwMode != STGM_READ))
return STG_E_ACCESSDENIED;
// at this point we have the lock if we need it, so we can close and reopen if we
// don't have it open with the right permissions...
if (_pStorageThumb)
{
if (_dwModeStorage == dwMode)
{
// we already have it open in this mode...
*pdwLock = AcquireLock();
return S_FALSE;
}
else
{
// we are open and the mode is different, so close it. Note, no lock is passed, we already
// have it
HRESULT hr = Close(NULL);
if (FAILED(hr))
{
return hr;
}
}
}
DWORD dwLock = AcquireLock();
DWORD dwFlags = GetAccessMode(dwMode, FALSE);
// now open the DocFile
HRESULT hr = StgOpenStorage(m_szPath, NULL, dwFlags, NULL, NULL, &_pStorageThumb);
if (SUCCEEDED(hr))
{
_dwModeStorage = dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE);
_SetAttribs(FALSE);
hr = LoadCatalog();
*pdwLock = dwLock;
}
if (STG_E_DOCFILECORRUPT == hr)
{
DeleteFile(m_szPath);
}
if (FAILED(hr))
{
ReleaseLock(dwLock);
}
return hr;
}
STDMETHODIMP CThumbStore::Create(DWORD dwMode, DWORD *pdwLock)
{
if (m_szPath[0] == 0)
{
return E_UNEXPECTED;
}
if (_pStorageThumb)
{
// we already have it open, so we can't create it ...
return STG_E_ACCESSDENIED;
}
if ((m_dwModeAllow == STGM_READ) && (dwMode != STGM_READ))
return STG_E_ACCESSDENIED;
DWORD dwLock = AcquireLock();
DWORD dwFlags = GetAccessMode(dwMode, FALSE);
HRESULT hr = StgCreateDocfile(m_szPath, dwFlags, NULL, &_pStorageThumb);
if (SUCCEEDED(hr))
{
_dwModeStorage = dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE);
_SetAttribs(FALSE);
*pdwLock = dwLock;
}
if (FAILED(hr))
{
ReleaseLock(dwLock);
}
return hr;
}
STDMETHODIMP CThumbStore::ReleaseLock(DWORD const *pdwLock)
{
ReleaseLock(*pdwLock);
return S_OK;
}
STDMETHODIMP CThumbStore::IsLocked()
{
return (m_fLocked > 0 ? S_OK : S_FALSE);
}
// pdwLock can be NULL indicating close the last opened lock
STDMETHODIMP CThumbStore::Close(DWORD const *pdwLock)
{
DWORD dwLock;
DWORD const *pdwRel = pdwLock;
if (!pdwLock)
{
dwLock = AcquireLock();
pdwRel = &dwLock;
}
HRESULT hr = S_FALSE;
if (_pStorageThumb)
{
if (_dwModeStorage != STGM_READ)
{
// write out the new catalog...
hr = Commit(pdwLock);
_pStorageThumb->Commit(0);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, m_szPath, NULL); // suppress the update dir
}
_pStorageThumb->Release();
_pStorageThumb = NULL;
}
ReleaseLock(*pdwRel);
return hr;
}
// pdwLock can be NULL meaning use the current lock
STDMETHODIMP CThumbStore::Commit(DWORD const *pdwLock)
{
DWORD dwLock;
if (!pdwLock)
{
dwLock = AcquireLock();
pdwLock = &dwLock;
}
HRESULT hr = S_FALSE;
if (_pStorageThumb && _dwModeStorage != STGM_READ)
{
if (m_dwCatalogChange)
{
SaveCatalog();
}
hr = S_OK;
}
ReleaseLock(*pdwLock);
return hr;
}
STDMETHODIMP CThumbStore::GetMode(DWORD *pdwMode)
{
if (!pdwMode)
{
return E_INVALIDARG;
}
if (_pStorageThumb)
{
*pdwMode = _dwModeStorage;
return S_OK;
}
*pdwMode = 0;
return S_FALSE;
}
STDMETHODIMP CThumbStore::GetCapabilities(DWORD *pdwMode)
{
ASSERT(pdwMode);
// right now, both are needed/supported for thumbs.db
*pdwMode = SHIMSTCAPFLAG_LOCKABLE | SHIMSTCAPFLAG_PURGEABLE;
return S_OK;
}
STDMETHODIMP CThumbStore::AddEntry(LPCWSTR pszName, const FILETIME *pftTimeStamp, DWORD dwMode, HBITMAP hImage)
{
ASSERT(pszName);
if (!_pStorageThumb)
{
return E_UNEXPECTED;
}
if (_dwModeStorage == STGM_READ)
{
// can't modify in this mode...
return E_ACCESSDENIED;
}
// this will block unless we already have the lock on this thread...
DWORD dwLock = AcquireLock();
DWORD dwStream = 0;
CLISTPOS pCur = NULL;
CATALOG_ENTRY *pNode = NULL;
EnterCriticalSection(&m_csInternals);
if (FindStreamID(pszName, &dwStream, &pNode) != S_OK)
{
// needs adding to the catalog...
UINT cbSize = sizeof(*pNode) + lstrlenW(pszName) * sizeof(WCHAR);
pNode = (CATALOG_ENTRY *)LocalAlloc(LPTR, cbSize);
if (pNode == NULL)
{
LeaveCriticalSection(&m_csInternals);
ReleaseLock(dwLock);
return E_OUTOFMEMORY;
}
pNode->cbSize = cbSize;
if (pftTimeStamp)
{
pNode->ftTimeStamp = *pftTimeStamp;
}
dwStream = pNode->dwIndex = ++m_dwMaxIndex;
StrCpyW(pNode->szName, pszName);
pCur = m_rgCatalog.AddTail(pNode);
if (pCur == NULL)
{
LocalFree(pNode);
LeaveCriticalSection(&m_csInternals);
ReleaseLock(dwLock);
return E_OUTOFMEMORY;
}
m_rgHeader.dwEntryCount++;
}
else if (pftTimeStamp)
{
// update the timestamp .....
pNode->ftTimeStamp = *pftTimeStamp;
}
LeaveCriticalSection(&m_csInternals);
IStream *pStream = NULL;
HRESULT hr = THR(GetEntryStream(dwStream, dwMode, &pStream));
if (SUCCEEDED(hr))
{
hr = THR(WriteImage(pStream, hImage));
pStream->Release();
}
if (FAILED(hr) && pCur)
{
// take it back out of the list if we added it...
EnterCriticalSection(&m_csInternals);
m_rgCatalog.RemoveAt(pCur);
m_rgHeader.dwEntryCount--;
LeaveCriticalSection(&m_csInternals);
LocalFree(pNode);
}
if (SUCCEEDED(hr))
{
// catalog change....
m_dwCatalogChange++;
}
ReleaseLock(dwLock);
return hr;
}
STDMETHODIMP CThumbStore::GetEntry(LPCWSTR pszName, DWORD dwMode, HBITMAP *phImage)
{
if (!_pStorageThumb)
{
return E_UNEXPECTED;
}
HRESULT hr;
DWORD dwStream;
if (FindStreamID(pszName, &dwStream, NULL) != S_OK)
{
hr = E_FAIL;
}
else
{
IStream *pStream;
hr = GetEntryStream(dwStream, dwMode, &pStream);
if (SUCCEEDED(hr))
{
hr = ReadImage(pStream, phImage);
pStream->Release();
}
}
return hr;
}
BOOL CThumbStore::_MatchNodeName(CATALOG_ENTRY *pNode, LPCWSTR pszName)
{
return (StrCmpIW(pNode->szName, pszName) == 0) ||
(StrCmpIW(PathFindFileName(pNode->szName), pszName) == 0); // match old thumbs.db files
}
STDMETHODIMP CThumbStore::DeleteEntry(LPCWSTR pszName)
{
if (!_pStorageThumb)
{
return E_UNEXPECTED;
}
if (_dwModeStorage == STGM_READ)
{
// can't modify in this mode...
return E_ACCESSDENIED;
}
DWORD dwLock = AcquireLock();
EnterCriticalSection(&m_csInternals);
// check to see if it already exists.....
CATALOG_ENTRY *pNode = NULL;
CLISTPOS pCur = m_rgCatalog.GetHeadPosition();
while (pCur != NULL)
{
CLISTPOS pDel = pCur;
pNode = m_rgCatalog.GetNext(pCur);
ASSERT(pNode != NULL);
if (_MatchNodeName(pNode, pszName))
{
m_rgCatalog.RemoveAt(pDel);
m_rgHeader.dwEntryCount--;
m_dwCatalogChange++;
if (pNode->dwIndex == m_dwMaxIndex)
{
m_dwMaxIndex--;
}
LeaveCriticalSection(&m_csInternals);
WCHAR szStream[30];
GenerateStreamName(szStream, ARRAYSIZE(szStream), pNode->dwIndex);
_pStorageThumb->DestroyElement(szStream);
LocalFree(pNode);
ReleaseLock(dwLock);
return S_OK;
}
}
LeaveCriticalSection(&m_csInternals);
ReleaseLock(dwLock);
return E_INVALIDARG;
}
STDMETHODIMP CThumbStore::IsEntryInStore(LPCWSTR pszName, FILETIME *pftTimeStamp)
{
if (!_pStorageThumb)
{
return E_UNEXPECTED;
}
DWORD dwStream = 0;
CATALOG_ENTRY *pNode = NULL;
EnterCriticalSection(&m_csInternals);
HRESULT hr = FindStreamID(pszName, &dwStream, &pNode);
if (pftTimeStamp && SUCCEEDED(hr))
{
ASSERT(pNode);
*pftTimeStamp = pNode->ftTimeStamp;
}
LeaveCriticalSection(&m_csInternals);
return (hr == S_OK) ? S_OK : S_FALSE;
}
STDMETHODIMP CThumbStore::Enum(IEnumShellImageStore **ppEnum)
{
return CEnumThumbStore_Create(this, ppEnum);
}
HRESULT CThumbStore::FindStreamID(LPCWSTR pszName, DWORD *pdwStream, CATALOG_ENTRY ** ppNode)
{
// check to see if it already exists in the catalog.....
CATALOG_ENTRY *pNode = NULL;
CLISTPOS pCur = m_rgCatalog.GetHeadPosition();
while (pCur != NULL)
{
pNode = m_rgCatalog.GetNext(pCur);
ASSERT(pNode != NULL);
if (_MatchNodeName(pNode, pszName))
{
*pdwStream = pNode->dwIndex;
if (ppNode != NULL)
{
*ppNode = pNode;
}
return S_OK;
}
}
return E_FAIL;
}
CEnumThumbStore::CEnumThumbStore()
{
m_pStore = NULL;
m_pPos = 0;
m_dwCatalogChange = 0;
}
CEnumThumbStore::~CEnumThumbStore()
{
if (m_pStore)
{
SAFECAST(m_pStore, IPersistFile *)->Release();
}
}
STDMETHODIMP CEnumThumbStore::Reset(void)
{
m_pPos = m_pStore->m_rgCatalog.GetHeadPosition();
m_dwCatalogChange = m_pStore->m_dwCatalogChange;
return S_OK;
}
STDMETHODIMP CEnumThumbStore::Next(ULONG celt, PENUMSHELLIMAGESTOREDATA * prgElt, ULONG * pceltFetched)
{
if ((celt > 1 && !pceltFetched) || !celt)
{
return E_INVALIDARG;
}
if (m_dwCatalogChange != m_pStore->m_dwCatalogChange)
{
return E_UNEXPECTED;
}
ULONG celtFetched = 0;
while (celtFetched < celt &&m_pPos)
{
CThumbStore::CATALOG_ENTRY *pNode = m_pStore->m_rgCatalog.GetNext(m_pPos);
ASSERT(pNode);
PENUMSHELLIMAGESTOREDATA pElt = (PENUMSHELLIMAGESTOREDATA) CoTaskMemAlloc(sizeof(ENUMSHELLIMAGESTOREDATA));
if (!pElt)
{
// cleanup others...
for (ULONG celtCleanup = 0; celtCleanup < celtFetched; celtCleanup++)
{
CoTaskMemFree(prgElt[celtCleanup]);
prgElt[celtCleanup] = NULL;
}
return E_OUTOFMEMORY;
}
StrCpyN(pElt->szPath, pNode->szName, MAX_PATH);
pElt->ftTimeStamp = pNode->ftTimeStamp;
ASSERT(!IsBadWritePtr((void *) (prgElt + celtFetched), sizeof(PENUMSHELLIMAGESTOREDATA)));
prgElt[celtFetched] = pElt;
celtFetched++;
}
if (pceltFetched)
{
*pceltFetched = celtFetched;
}
if (!celtFetched)
{
return E_FAIL;
}
return (celtFetched < celt) ? S_FALSE : S_OK;
}
STDMETHODIMP CEnumThumbStore::Skip(ULONG celt)
{
if (!celt)
{
return E_INVALIDARG;
}
if (m_dwCatalogChange != m_pStore->m_dwCatalogChange)
{
return E_UNEXPECTED;
}
ULONG celtSkipped = 0;
while (celtSkipped < celt &&m_pPos)
{
m_pStore->m_rgCatalog.GetNext(m_pPos);
}
if (!celtSkipped)
{
return E_FAIL;
}
return (celtSkipped < celt) ? S_FALSE : S_OK;
}
STDMETHODIMP CEnumThumbStore::Clone(IEnumShellImageStore ** ppEnum)
{
CEnumThumbStore * pEnum = new CComObject<CEnumThumbStore>;
if (!pEnum)
{
return E_OUTOFMEMORY;
}
((IPersistFile *)m_pStore)->AddRef();
pEnum->m_pStore = m_pStore;
pEnum->m_dwCatalogChange = m_dwCatalogChange;
// created with zero ref count....
pEnum->AddRef();
*ppEnum = SAFECAST(pEnum, IEnumShellImageStore *);
return S_OK;
}
HRESULT CEnumThumbStore_Create(CThumbStore * pThis, IEnumShellImageStore ** ppEnum)
{
CEnumThumbStore * pEnum = new CComObject<CEnumThumbStore>;
if (!pEnum)
{
return E_OUTOFMEMORY;
}
((IPersistFile *)pThis)->AddRef();
pEnum->m_pStore = pThis;
// created with zero ref count....
pEnum->AddRef();
*ppEnum = SAFECAST(pEnum, IEnumShellImageStore *);
return S_OK;
}
HRESULT Version1ReadImage(IStream *pStream, DWORD cbSize, HBITMAP *phImage)
{
*phImage = NULL;
HRESULT hr = E_OUTOFMEMORY;
BITMAPINFO *pbi = (BITMAPINFO *) LocalAlloc(LPTR, cbSize);
if (pbi)
{
hr = IStream_Read(pStream, pbi, cbSize);
if (SUCCEEDED(hr))
{
HDC hdc = GetDC(NULL);
if (hdc)
{
*phImage = CreateDIBitmap(hdc, &(pbi->bmiHeader), CBM_INIT, CalcBitsOffsetInDIB(pbi), pbi, DIB_RGB_COLORS);
ReleaseDC(NULL, hdc);
hr = S_OK;
}
}
LocalFree(pbi);
}
return hr;
}
HRESULT CThumbStore::ReadImage(IStream *pStream, HBITMAP *phImage)
{
STREAM_HEADER rgHead;
HRESULT hr = IStream_Read(pStream, &rgHead, sizeof(rgHead));
if (SUCCEEDED(hr))
{
if (rgHead.cbSize == sizeof(rgHead))
{
if (rgHead.dwFlags == STREAMFLAGS_DIB)
{
hr = Version1ReadImage(pStream, rgHead.ulSize, phImage);
}
else if (rgHead.dwFlags == STREAMFLAGS_JPEG)
{
void *pBits = LocalAlloc(LPTR, rgHead.ulSize);
if (pBits)
{
hr = IStream_Read(pStream, pBits, rgHead.ulSize);
if (SUCCEEDED(hr))
{
if (!DecompressImage(pBits, rgHead.ulSize, phImage))
{
hr = E_UNEXPECTED;
}
}
LocalFree(pBits);
}
else
{
hr = E_OUTOFMEMORY;
}
}
else
{
return E_FAIL;
}
}
else
{
hr = E_FAIL;
}
}
return hr;
}
HRESULT Version1WriteImage(IStream *pStream, HBITMAP hImage)
{
HRESULT hr;
BITMAPINFO *pBitmap = BitmapToDIB(hImage);
if (pBitmap)
{
int ncolors = pBitmap->bmiHeader.biClrUsed;
if (ncolors == 0 && pBitmap->bmiHeader.biBitCount <= 8)
ncolors = 1 << pBitmap->bmiHeader.biBitCount;
if (pBitmap->bmiHeader.biBitCount == 16 ||
pBitmap->bmiHeader.biBitCount == 32)
{
if (pBitmap->bmiHeader.biCompression == BI_BITFIELDS)
{
ncolors = 3;
}
}
int iOffset = ncolors * sizeof(RGBQUAD);
STREAM_HEADER rgHead;
rgHead.cbSize = sizeof(rgHead);
rgHead.dwFlags = STREAMFLAGS_DIB;
rgHead.ulSize = pBitmap->bmiHeader.biSize + iOffset + pBitmap->bmiHeader.biSizeImage;
hr = IStream_Write(pStream, &rgHead, sizeof(rgHead));
if (SUCCEEDED(hr))
{
hr = IStream_Write(pStream, pBitmap, rgHead.ulSize);
}
LocalFree(pBitmap);
}
else
{
hr = E_UNEXPECTED;
}
return hr;
}
HRESULT CThumbStore::WriteImage(IStream *pStream, HBITMAP hImage)
{
void *pBits;
ULONG ulBuffer;
HRESULT hr;
if (CompressImage(hImage, &pBits, &ulBuffer))
{
STREAM_HEADER rgHead;
rgHead.cbSize = sizeof(rgHead);
rgHead.dwFlags = STREAMFLAGS_JPEG;
rgHead.ulSize = ulBuffer;
hr = IStream_Write(pStream, &rgHead, sizeof(rgHead));
if (SUCCEEDED(hr))
{
hr = IStream_Write(pStream, pBits, ulBuffer);
}
CoTaskMemFree(pBits);
}
else
hr = E_FAIL;
return hr;
}
HRESULT CThumbStore::GetEntryStream(DWORD dwStream, DWORD dwMode, IStream **ppStream)
{
WCHAR szStream[30];
GenerateStreamName(szStream, ARRAYSIZE(szStream), dwStream);
// leave only the STG_READ | STGM_READWRITE | STGM_WRITE modes
dwMode &= STGM_READ | STGM_WRITE | STGM_READWRITE;
if (!_pStorageThumb)
{
return E_UNEXPECTED;
}
if (_dwModeStorage != STGM_READWRITE && dwMode != _dwModeStorage)
{
return E_ACCESSDENIED;
}
DWORD dwFlags = GetAccessMode(dwMode, TRUE);
if (dwFlags & STGM_WRITE)
{
_pStorageThumb->DestroyElement(szStream);
return _pStorageThumb->CreateStream(szStream, dwFlags, NULL, NULL, ppStream);
}
else
{
return _pStorageThumb->OpenStream(szStream, NULL, dwFlags, NULL, ppStream);
}
}
DWORD CThumbStore::GetAccessMode(DWORD dwMode, BOOL fStream)
{
dwMode &= STGM_READ | STGM_WRITE | STGM_READWRITE;
DWORD dwFlags = dwMode;
// the root only needs Deny_Write, streams need exclusive....
if (dwMode == STGM_READ && !fStream)
{
dwFlags |= STGM_SHARE_DENY_WRITE;
}
else
{
dwFlags |= STGM_SHARE_EXCLUSIVE;
}
return dwFlags;
}
BITMAPINFO *BitmapToDIB(HBITMAP hBmp)
{
HDC hdcWnd = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hdcWnd);
BITMAPINFO bi;
BITMAP Bitmap;
GetObject(hBmp, sizeof(Bitmap), (LPSTR)&Bitmap);
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biBitCount = 0;
int iVal = GetDIBits(hMemDC, hBmp, 0, Bitmap.bmHeight, NULL, &bi, DIB_RGB_COLORS);
int ncolors = bi.bmiHeader.biClrUsed;
if (ncolors == 0 && bi.bmiHeader.biBitCount <= 8)
ncolors = 1 << bi.bmiHeader.biBitCount;
if (bi.bmiHeader.biBitCount == 16 ||
bi.bmiHeader.biBitCount == 32)
{
if (bi.bmiHeader.biCompression == BI_BITFIELDS)
{
ncolors = 3;
}
}
int iOffset = ncolors * sizeof(RGBQUAD);
void *pBuffer = LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + iOffset + bi.bmiHeader.biSizeImage);
if (pBuffer)
{
BITMAPINFO * pbi = (BITMAPINFO *)pBuffer;
void *lpBits = (BYTE *)pBuffer + iOffset + sizeof(BITMAPINFOHEADER);
// copy members of what was returned in last GetDIBits call.
CopyMemory(&pbi->bmiHeader, &bi.bmiHeader, sizeof(BITMAPINFOHEADER));
iVal = GetDIBits(hMemDC, hBmp, 0, Bitmap.bmHeight, lpBits,
pbi, DIB_RGB_COLORS);
}
DeleteDC(hMemDC);
ReleaseDC(NULL, hdcWnd);
return (BITMAPINFO *)pBuffer;
}
BOOL CThumbStore::InitCodec(void)
{
if (NULL == m_pJPEGCodec)
{
m_pJPEGCodec = new CThumbnailFCNContainer();
}
return (NULL != m_pJPEGCodec);
}
BOOL CThumbStore::CompressImage(HBITMAP hBmp, void **ppvOutBuffer, ULONG *plBufSize)
{
// given an HBITMAP, get its data....
HBITMAP hBmpOut = hBmp;
void *pBits;
SIZE rgBmpSize;
*ppvOutBuffer = NULL;
if (!InitCodec())
{
return FALSE;
}
HRESULT hr = PrepImage(&hBmpOut, &rgBmpSize, &pBits);
if (SUCCEEDED(hr))
{
ASSERT(pBits);
hr = E_FAIL;
if (pBits)
{
hr = m_pJPEGCodec->EncodeThumbnail(pBits, rgBmpSize.cx, rgBmpSize.cy,
ppvOutBuffer, plBufSize);
}
if (hBmpOut != hBmp)
{
// free the DIBSECTION we were passed back...
DeleteObject(hBmpOut);
}
}
return SUCCEEDED(hr);
}
BOOL CThumbStore::DecompressImage(void *pvInBuffer, ULONG ulBufferSize, HBITMAP *phBmp)
{
if (!InitCodec())
{
return FALSE;
}
ULONG ulWidth;
ULONG ulHeight;
HRESULT hr = m_pJPEGCodec->DecodeThumbnail(phBmp, &ulWidth, &ulHeight, pvInBuffer, ulBufferSize);
return SUCCEEDED(hr);
}
HRESULT CThumbStore::PrepImage(HBITMAP *phBmp, SIZE * prgSize, void ** ppBits)
{
ASSERT(phBmp && &phBmp);
ASSERT(prgSize);
ASSERT(ppBits);
DIBSECTION rgDIB;
*ppBits = NULL;
HRESULT hr = E_FAIL;
// is the image the wrong colour depth or not a DIBSECTION
if (!GetObject(*phBmp, sizeof(rgDIB), &rgDIB) || (rgDIB.dsBmih.biBitCount != 32))
{
HBITMAP hBmp;
BITMAP rgBmp;
GetObject(*phBmp, sizeof(rgBmp), &rgBmp);
prgSize->cx = rgBmp.bmWidth;
prgSize->cy = rgBmp.bmHeight;
// generate a 32 bit DIB of the right size
if (!CreateSizedDIBSECTION(prgSize, 32, NULL, NULL, &hBmp, NULL, ppBits))
{
return E_OUTOFMEMORY;
}
HDC hDCMem1 = CreateCompatibleDC(NULL);
if (hDCMem1)
{
HDC hDCMem2 = CreateCompatibleDC(NULL);
if (hDCMem2)
{
HBITMAP hBmpOld1 = (HBITMAP) SelectObject(hDCMem1, *phBmp);
HBITMAP hBmpOld2 = (HBITMAP) SelectObject(hDCMem2, hBmp);
// copy the image accross to generate the right sized DIB
BitBlt(hDCMem2, 0, 0, prgSize->cx, prgSize->cy, hDCMem1, 0, 0, SRCCOPY);
SelectObject(hDCMem1, hBmpOld1);
SelectObject(hDCMem2, hBmpOld2);
DeleteDC(hDCMem2);
}
DeleteDC(hDCMem1);
}
// pass back the BMP so it can be destroyed later...
*phBmp = hBmp;
return S_OK;
}
else
{
// HOUSTON, we have a DIBSECTION, this is quicker.....
*ppBits = rgDIB.dsBm.bmBits;
prgSize->cx = rgDIB.dsBm.bmWidth;
prgSize->cy = rgDIB.dsBm.bmHeight;
return S_OK;
}
}
HRESULT DeleteFileThumbnail(LPCWSTR szFilePath)
{
WCHAR szFolder[MAX_PATH];
WCHAR *szFile;
HRESULT hr = S_OK;
StrCpyNW(szFolder, szFilePath, ARRAYSIZE(szFolder));
if (SUCCEEDED(hr))
{
szFile = PathFindFileName(szFolder);
if (szFile != szFolder)
{
*(szFile - 1) = 0; // NULL terminates folder
IShellImageStore *pDiskCache = NULL;
hr = LoadFromFile(CLSID_ShellThumbnailDiskCache, szFolder, IID_PPV_ARG(IShellImageStore, &pDiskCache));
if (SUCCEEDED(hr))
{
IPersistFile *pPersist = NULL;
hr = pDiskCache->QueryInterface(IID_PPV_ARG(IPersistFile, &pPersist));
if (SUCCEEDED(hr))
{
hr = pPersist->Load(szFolder, STGM_READWRITE);
if (SUCCEEDED(hr))
{
DWORD dwLock;
hr = pDiskCache->Open(STGM_READWRITE, &dwLock);
if (SUCCEEDED(hr))
{
hr = pDiskCache->DeleteEntry(szFile);
pDiskCache->ReleaseLock(&dwLock);
pDiskCache->Close(NULL);
}
}
pPersist->Release();
}
pDiskCache->Release();
}
}
else
{
hr = E_FAIL;
}
}
return hr;
}