|
|
#include "shellprv.h"
#pragma hdrstop
#include "debug.h"
#include "stgutil.h"
#include "ids.h"
#include "tlist.h"
#include "thumbutil.h"
#include <gdiplus\gdiplus.h>
using namespace Gdiplus;
#define THUMBNAIL_QUALITY 90
__inline HRESULT HR_FROM_STATUS(Status status) { return ((status) == Ok) ? S_OK : E_FAIL; }
class CGraphicsInit { ULONG_PTR _token; public: CGraphicsInit() { _token = 0; GdiplusStartupInput gsi; GdiplusStartup(&_token, &gsi, NULL); }; ~CGraphicsInit() { if (_token != 0) { GdiplusShutdown(_token); } }; };
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);
HRESULT DecompressImage(IStream *pStream, HBITMAP *phBmp); HRESULT CompressImage(IStream *pStream, HBITMAP hBmp);
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; BOOL m_bCSInternalsInited; // 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; BOOL m_bCSLockInited;
DWORD m_dwLock; int m_fLocked; // gdi+ jpg decoder variables
CGraphicsInit m_cgi; // initializes gdi+
int m_iThumbnailQuality; // jpg image quality with valid range [0 to 100]
};
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 0x0007
#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 cchSize, DWORD dwNumber);
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; m_bCSLockInited = InitializeCriticalSectionAndSpinCount(&m_csLock, 0); m_bCSInternalsInited = InitializeCriticalSectionAndSpinCount(&m_csInternals, 0);
m_iThumbnailQuality = THUMBNAIL_QUALITY; int qual = 0; DWORD cb = sizeof(qual); SHRegGetUSValue(TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"), TEXT("ThumbnailQuality"), NULL, &qual, &cb, FALSE, NULL, 0); if (qual >= 50 && qual <= 100) // constrain to reason
{ m_iThumbnailQuality = qual; } }
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(); }
// assume these are free, we are at ref count zero, no one should still be calling us...
if (m_bCSLockInited) { DeleteCriticalSection(&m_csLock); } if (m_bCSInternalsInited) { 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) { ASSERT(m_bCSLockInited); 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) { ASSERT(m_bCSLockInited); 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)) { if (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; } } else { hr = E_UNEXPECTED; // Corrupted header information
} } } } }
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() { HRESULT hr = E_UNEXPECTED; if (_pStorageThumb) { _pStorageThumb->DestroyElement(CATALOG_STREAM);
IStream *pCatalog; 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 cchSize, DWORD dwNumber) { UINT cPos = 0; while ((dwNumber > 0) && (cPos < cchSize)) { 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 = E_UNEXPECTED; if (m_bCSInternalsInited && m_bCSLockInited) { 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; return StringCchCopyW(m_szPath, ARRAYSIZE(m_szPath), pszPath); }
STDMETHODIMP CThumbStore::Load(LPCWSTR pszFileName, DWORD dwMode) { TCHAR szPath[MAX_PATH]; HRESULT hr = E_UNEXPECTED; if (PathCombine(szPath, pszFileName, THUMB_FILENAME)) { hr = _InitFromPath(szPath, dwMode); } return hr; }
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]; if (SUCCEEDED(StringCchCopyW(szStream, ARRAYSIZE(szStream), m_szPath))) { if (SUCCEEDED(StringCchCatW(szStream, ARRAYSIZE(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(NULL); _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 cchName = lstrlenW(pszName) + 1; UINT cbSize = sizeof(*pNode) + (cchName - 1) * sizeof(WCHAR); // subtract 1 since *pNode has a WCHAR[1]
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;
if (SUCCEEDED(StringCchCopyW(pNode->szName, cchName, 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; HRESULT hr = S_OK;
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) { hr = E_OUTOFMEMORY; break; }
hr = StringCchCopyW(pElt->szPath, ARRAYSIZE(pElt->szPath), pNode->szName); if (FAILED(hr)) { CoTaskMemFree(pElt); break; } pElt->ftTimeStamp = pNode->ftTimeStamp;
prgElt[celtFetched] = pElt;
celtFetched++; }
if (FAILED(hr) && celtFetched) { // cleanup
for (ULONG celtCleanup = 0; celtCleanup < celtFetched; celtCleanup++) { CoTaskMemFree(prgElt[celtCleanup]); prgElt[celtCleanup] = NULL; } celtFetched = 0; }
if (pceltFetched) { *pceltFetched = celtFetched; }
if (FAILED(hr)) return hr; 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) { // gdi+ will only accept the jpg stream if it's at the start of the
// stream. We copy the jpeg into its own stream.
IStream *pstmMem; hr = CreateStreamOnHGlobal(NULL, TRUE, &pstmMem); if (SUCCEEDED(hr)) { ULARGE_INTEGER ulSize = { rgHead.ulSize }; hr = pStream->CopyTo(pstmMem, ulSize, NULL, NULL); if (SUCCEEDED(hr)) { LARGE_INTEGER liSeek = {0, 0}; hr = pstmMem->Seek(liSeek, STREAM_SEEK_SET, NULL); if (SUCCEEDED(hr)) { hr = DecompressImage(pstmMem, phImage); } } pstmMem->Release(); } } else { hr = E_FAIL; } } else { hr = E_FAIL; } } return hr; }
HRESULT CThumbStore::WriteImage(IStream *pStream, HBITMAP hImage) { STREAM_HEADER rgHead; // skip past the header. It will be writen when we know the image size.
LARGE_INTEGER liSeek = { sizeof(rgHead) }; HRESULT hr = pStream->Seek(liSeek, STREAM_SEEK_SET, NULL); if (SUCCEEDED(hr)) { hr = CompressImage(pStream, hImage); if (SUCCEEDED(hr)) { STATSTG stat; hr = pStream->Stat(&stat, STATFLAG_NONAME); if (SUCCEEDED(hr)) { //now write the header
rgHead.cbSize = sizeof(rgHead); rgHead.dwFlags = STREAMFLAGS_JPEG; rgHead.ulSize = stat.cbSize.QuadPart - sizeof(rgHead); //move to the beginning of the stream to write the header
liSeek.QuadPart = 0; hr = pStream->Seek(liSeek, STREAM_SEEK_SET, NULL); if (SUCCEEDED(hr)) { hr = IStream_Write(pStream, &rgHead, sizeof(rgHead)); } } } } 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; }
const CLSID CLSID_JPEGCodec = { 0x557cf401, 0x1a04, 0x11d3, {0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e}};
HRESULT CThumbStore::CompressImage(IStream *pStream, HBITMAP hBmp) { HRESULT hr = E_FAIL; // create the gdi+ bitmap
Bitmap* pBitmap = new Bitmap(hBmp, NULL); if (pBitmap) { // Set the JPG Quailty
EncoderParameters ep;
ep.Parameter[0].Guid = EncoderQuality; ep.Parameter[0].Type = EncoderParameterValueTypeLong; ep.Parameter[0].NumberOfValues = 1; ep.Parameter[0].Value = &m_iThumbnailQuality; ep.Count = 1;
// Save the Bitmap to the stream in JPG format
hr = HR_FROM_STATUS(pBitmap->Save(pStream, &CLSID_JPEGCodec, &ep)); delete pBitmap; } return hr; }
HRESULT CThumbStore::DecompressImage(IStream *pStream, HBITMAP *phBmp) { HRESULT hr = E_FAIL; // Create gdi+ Bitmap from image stream
Bitmap* pBitmap = new Bitmap(pStream, true); if (pBitmap) { hr = HR_FROM_STATUS(pBitmap->GetHBITMAP(Color::Black, phBmp)); delete pBitmap; } return hr; }
HRESULT DeleteFileThumbnail(LPCWSTR szFilePath) { WCHAR szFolder[MAX_PATH]; WCHAR *szFile; HRESULT hr = E_FAIL;
hr = StringCchCopyW(szFolder, ARRAYSIZE(szFolder), szFilePath); 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->Close(&dwLock); } } pPersist->Release(); } pDiskCache->Release(); }
} else { hr = E_FAIL; } }
return hr; }
|