You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1381 lines
37 KiB
1381 lines
37 KiB
#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;
|
|
}
|
|
|