1471 lines
37 KiB
C++
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;
|
|
}
|
|
|