Leaked source code of windows server 2003
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

  1. #include "shellprv.h"
  2. #pragma hdrstop
  3. #include "debug.h"
  4. #include "stgutil.h"
  5. #include "ids.h"
  6. #include "tlist.h"
  7. #include "thumbutil.h"
  8. #include <gdiplus\gdiplus.h>
  9. using namespace Gdiplus;
  10. #define THUMBNAIL_QUALITY 90
  11. __inline HRESULT HR_FROM_STATUS(Status status)
  12. {
  13. return ((status) == Ok) ? S_OK : E_FAIL;
  14. }
  15. class CGraphicsInit
  16. {
  17. ULONG_PTR _token;
  18. public:
  19. CGraphicsInit()
  20. {
  21. _token = 0;
  22. GdiplusStartupInput gsi;
  23. GdiplusStartup(&_token, &gsi, NULL);
  24. };
  25. ~CGraphicsInit()
  26. {
  27. if (_token != 0)
  28. {
  29. GdiplusShutdown(_token);
  30. }
  31. };
  32. };
  33. void SHGetThumbnailSizeForThumbsDB(SIZE *psize);
  34. STDAPI CThumbStore_CreateInstance(IUnknown* punkOuter, REFIID riid, void **ppv);
  35. class CThumbStore : public IShellImageStore,
  36. public IPersistFolder,
  37. public IPersistFile,
  38. public CComObjectRootEx<CComMultiThreadModel>,
  39. public CComCoClass< CThumbStore,&CLSID_ShellThumbnailDiskCache >
  40. {
  41. struct CATALOG_ENTRY
  42. {
  43. DWORD cbSize;
  44. DWORD dwIndex;
  45. FILETIME ftTimeStamp;
  46. WCHAR szName[1];
  47. };
  48. struct CATALOG_HEADER
  49. {
  50. WORD cbSize;
  51. WORD wVersion;
  52. DWORD dwEntryCount;
  53. SIZE szThumbnailExtent;
  54. };
  55. public:
  56. BEGIN_COM_MAP(CThumbStore)
  57. COM_INTERFACE_ENTRY_IID(IID_IShellImageStore,IShellImageStore)
  58. COM_INTERFACE_ENTRY(IPersistFolder)
  59. COM_INTERFACE_ENTRY(IPersistFile)
  60. END_COM_MAP()
  61. DECLARE_NOT_AGGREGATABLE(CThumbStore)
  62. CThumbStore();
  63. ~CThumbStore();
  64. // IPersist
  65. STDMETHOD(GetClassID)(CLSID *pClassID);
  66. // IPersistFolder
  67. STDMETHOD(Initialize)(LPCITEMIDLIST pidl);
  68. // IPersistFile
  69. STDMETHOD (IsDirty)(void);
  70. STDMETHOD (Load)(LPCWSTR pszFileName, DWORD dwMode);
  71. STDMETHOD (Save)(LPCWSTR pszFileName, BOOL fRemember);
  72. STDMETHOD (SaveCompleted)(LPCWSTR pszFileName);
  73. STDMETHOD (GetCurFile)(LPWSTR *ppszFileName);
  74. // IImageCache
  75. STDMETHOD (Open)(DWORD dwMode, DWORD *pdwLock);
  76. STDMETHOD (Create)(DWORD dwMode, DWORD *pdwLock);
  77. STDMETHOD (Close)(DWORD const *pdwLock);
  78. STDMETHOD (Commit)(DWORD const *pdwLock);
  79. STDMETHOD (ReleaseLock)(DWORD const *pdwLock);
  80. STDMETHOD (IsLocked)(THIS);
  81. STDMETHOD (GetMode)(DWORD *pdwMode);
  82. STDMETHOD (GetCapabilities)(DWORD *pdwCapMask);
  83. STDMETHOD (AddEntry)(LPCWSTR pszName, const FILETIME *pftTimeStamp, DWORD dwMode, HBITMAP hImage);
  84. STDMETHOD (GetEntry)(LPCWSTR pszName, DWORD dwMode, HBITMAP *phImage);
  85. STDMETHOD (DeleteEntry)(LPCWSTR pszName);
  86. STDMETHOD (IsEntryInStore)(LPCWSTR pszName, FILETIME *pftTimeStamp);
  87. STDMETHOD (Enum)(IEnumShellImageStore ** ppEnum);
  88. protected:
  89. friend class CEnumThumbStore;
  90. HRESULT LoadCatalog(void);
  91. HRESULT SaveCatalog(void);
  92. HRESULT FindStreamID(LPCWSTR pszName, DWORD *pdwStream, CATALOG_ENTRY **ppEntry);
  93. HRESULT GetEntryStream(DWORD dwStream, DWORD dwMode, IStream **ppStream);
  94. DWORD GetAccessMode(DWORD dwMode, BOOL fStream);
  95. DWORD AcquireLock(void);
  96. void ReleaseLock(DWORD dwLock);
  97. HRESULT DecompressImage(IStream *pStream, HBITMAP *phBmp);
  98. HRESULT CompressImage(IStream *pStream, HBITMAP hBmp);
  99. HRESULT WriteImage(IStream *pStream, HBITMAP hBmp);
  100. HRESULT ReadImage(IStream *pStream, HBITMAP *phBmp);
  101. BOOL _MatchNodeName(CATALOG_ENTRY *pNode, LPCWSTR pszName);
  102. HRESULT _InitFromPath(LPCTSTR pszPath, DWORD dwMode);
  103. void _SetAttribs(BOOL bForce);
  104. CATALOG_HEADER m_rgHeader;
  105. CList<CATALOG_ENTRY> m_rgCatalog;
  106. IStorage *_pStorageThumb;
  107. DWORD _dwModeStorage;
  108. DWORD m_dwModeAllow;
  109. WCHAR m_szPath[MAX_PATH];
  110. DWORD m_dwMaxIndex;
  111. DWORD m_dwCatalogChange;
  112. // Crit section used to protect the internals
  113. CRITICAL_SECTION m_csInternals;
  114. BOOL m_bCSInternalsInited;
  115. // needed for this object to be free-threaded... so that
  116. // we can query the catalog from the main thread whilst icons are
  117. // being read and written from the main thread.
  118. CRITICAL_SECTION m_csLock;
  119. BOOL m_bCSLockInited;
  120. DWORD m_dwLock;
  121. int m_fLocked;
  122. // gdi+ jpg decoder variables
  123. CGraphicsInit m_cgi; // initializes gdi+
  124. int m_iThumbnailQuality; // jpg image quality with valid range [0 to 100]
  125. };
  126. HRESULT CEnumThumbStore_Create(CThumbStore * pThis, IEnumShellImageStore ** ppEnum);
  127. class CEnumThumbStore : public IEnumShellImageStore,
  128. public CComObjectRoot
  129. {
  130. public:
  131. BEGIN_COM_MAP(CEnumThumbStore)
  132. COM_INTERFACE_ENTRY_IID(IID_IEnumShellImageStore,IEnumShellImageStore)
  133. END_COM_MAP()
  134. CEnumThumbStore();
  135. ~CEnumThumbStore();
  136. STDMETHOD (Reset)(void);
  137. STDMETHOD (Next)(ULONG celt, PENUMSHELLIMAGESTOREDATA *prgElt, ULONG *pceltFetched);
  138. STDMETHOD (Skip)(ULONG celt);
  139. STDMETHOD (Clone)(IEnumShellImageStore ** pEnum);
  140. protected:
  141. friend HRESULT CEnumThumbStore_Create(CThumbStore *pThis, IEnumShellImageStore **ppEnum);
  142. CThumbStore * m_pStore;
  143. CLISTPOS m_pPos;
  144. DWORD m_dwCatalogChange;
  145. };
  146. #define THUMB_FILENAME L"Thumbs.db"
  147. #define CATALOG_STREAM L"Catalog"
  148. #define CATALOG_VERSION 0x0007
  149. #define CATALOG_VERSION_XPGOLD 0x0005
  150. #define STREAMFLAGS_JPEG 0x0001
  151. #define STREAMFLAGS_DIB 0x0002
  152. struct STREAM_HEADER
  153. {
  154. DWORD cbSize;
  155. DWORD dwFlags;
  156. ULONG ulSize;
  157. };
  158. void GenerateStreamName(LPWSTR pszBuffer, DWORD cchSize, DWORD dwNumber);
  159. CThumbStore::CThumbStore()
  160. {
  161. m_szPath[0] = 0;
  162. m_rgHeader.dwEntryCount = 0;
  163. m_rgHeader.wVersion = CATALOG_VERSION;
  164. m_rgHeader.cbSize = sizeof(m_rgHeader);
  165. SHGetThumbnailSizeForThumbsDB(&m_rgHeader.szThumbnailExtent);
  166. m_dwMaxIndex = 0;
  167. m_dwModeAllow = STGM_READWRITE;
  168. // this counter is inc'd everytime the catalog changes so that we know when it
  169. // must be committed and so enumerators can detect the list has changed...
  170. m_dwCatalogChange = 0;
  171. m_fLocked = 0;
  172. m_bCSLockInited = InitializeCriticalSectionAndSpinCount(&m_csLock, 0);
  173. m_bCSInternalsInited = InitializeCriticalSectionAndSpinCount(&m_csInternals, 0);
  174. m_iThumbnailQuality = THUMBNAIL_QUALITY;
  175. int qual = 0;
  176. DWORD cb = sizeof(qual);
  177. SHRegGetUSValue(TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"),
  178. TEXT("ThumbnailQuality"), NULL, &qual, &cb, FALSE, NULL, 0);
  179. if (qual >= 50 && qual <= 100) // constrain to reason
  180. {
  181. m_iThumbnailQuality = qual;
  182. }
  183. }
  184. CThumbStore::~CThumbStore()
  185. {
  186. CLISTPOS pCur = m_rgCatalog.GetHeadPosition();
  187. while (pCur != NULL)
  188. {
  189. CATALOG_ENTRY *pNode = m_rgCatalog.GetNext(pCur);
  190. ASSERT(pNode != NULL);
  191. LocalFree((void *) pNode);
  192. }
  193. m_rgCatalog.RemoveAll();
  194. if (_pStorageThumb)
  195. {
  196. _pStorageThumb->Release();
  197. }
  198. // assume these are free, we are at ref count zero, no one should still be calling us...
  199. if (m_bCSLockInited)
  200. {
  201. DeleteCriticalSection(&m_csLock);
  202. }
  203. if (m_bCSInternalsInited)
  204. {
  205. DeleteCriticalSection(&m_csInternals);
  206. }
  207. }
  208. STDAPI CThumbStore_CreateInstance(IUnknown* punkOuter, REFIID riid, void **ppv)
  209. {
  210. return CComCreator< CComObject< CThumbStore > >::CreateInstance((void *)punkOuter, riid, (void **)ppv);
  211. }
  212. DWORD CThumbStore::AcquireLock(void)
  213. {
  214. ASSERT(m_bCSLockInited);
  215. EnterCriticalSection(&m_csLock);
  216. // inc the lock (we use a counter because we may reenter this on the same thread)
  217. m_fLocked++;
  218. // Never return a lock signature of zero, because that means "not locked".
  219. if (++m_dwLock == 0)
  220. ++m_dwLock;
  221. return m_dwLock;
  222. }
  223. void CThumbStore::ReleaseLock(DWORD dwLock)
  224. {
  225. ASSERT(m_bCSLockInited);
  226. if (dwLock)
  227. {
  228. ASSERT(m_fLocked);
  229. m_fLocked--;
  230. LeaveCriticalSection(&m_csLock);
  231. }
  232. }
  233. // the structure of the catalog is simple, it is a just a header stream
  234. HRESULT CThumbStore::LoadCatalog()
  235. {
  236. HRESULT hr;
  237. if (_pStorageThumb == NULL)
  238. {
  239. hr = E_UNEXPECTED;
  240. }
  241. else if (m_rgHeader.dwEntryCount != 0)
  242. {
  243. // it is already loaded....
  244. hr = S_OK;
  245. }
  246. else
  247. {
  248. // open the catalog stream...
  249. IStream *pCatalog;
  250. hr = _pStorageThumb->OpenStream(CATALOG_STREAM, NULL, GetAccessMode(STGM_READ, TRUE), NULL, &pCatalog);
  251. if (SUCCEEDED(hr))
  252. {
  253. EnterCriticalSection(&m_csInternals);
  254. // now read in the catalog from the stream ...
  255. hr = IStream_Read(pCatalog, &m_rgHeader, sizeof(m_rgHeader));
  256. if (SUCCEEDED(hr))
  257. {
  258. SIZE szCurrentSize;
  259. SHGetThumbnailSizeForThumbsDB(&szCurrentSize);
  260. if ((m_rgHeader.cbSize != sizeof(m_rgHeader)) || (m_rgHeader.wVersion != CATALOG_VERSION) ||
  261. (m_rgHeader.szThumbnailExtent.cx != szCurrentSize.cx) || (m_rgHeader.szThumbnailExtent.cy != szCurrentSize.cy))
  262. {
  263. if (m_rgHeader.wVersion == CATALOG_VERSION_XPGOLD)
  264. {
  265. hr = STG_E_DOCFILECORRUPT; // SECURITY: Many issues encrypting XPGOLD thumbnail databases, just delete it
  266. _pStorageThumb->Release();
  267. _pStorageThumb = NULL;
  268. }
  269. else
  270. {
  271. _SetAttribs(TRUE); // SECURITY: Old formats can't be encrypted
  272. hr = STG_E_OLDFORMAT;
  273. }
  274. }
  275. else
  276. {
  277. for (UINT iEntry = 0; (iEntry < m_rgHeader.dwEntryCount) && SUCCEEDED(hr); iEntry++)
  278. {
  279. DWORD cbSize;
  280. hr = IStream_Read(pCatalog, &cbSize, sizeof(cbSize));
  281. if (SUCCEEDED(hr))
  282. {
  283. if (cbSize <= sizeof(CATALOG_ENTRY) + sizeof(WCHAR) * MAX_PATH)
  284. {
  285. CATALOG_ENTRY *pEntry = (CATALOG_ENTRY *)LocalAlloc(LPTR, cbSize);
  286. if (pEntry)
  287. {
  288. pEntry->cbSize = cbSize;
  289. // read the rest with out the size on the front...
  290. hr = IStream_Read(pCatalog, ((BYTE *)pEntry + sizeof(cbSize)), cbSize - sizeof(cbSize));
  291. if (SUCCEEDED(hr))
  292. {
  293. CLISTPOS pCur = m_rgCatalog.AddTail(pEntry);
  294. if (pCur)
  295. {
  296. if (m_dwMaxIndex < pEntry->dwIndex)
  297. {
  298. m_dwMaxIndex = pEntry->dwIndex;
  299. }
  300. }
  301. else
  302. {
  303. hr = E_OUTOFMEMORY;
  304. }
  305. }
  306. }
  307. else
  308. {
  309. hr = E_OUTOFMEMORY;
  310. }
  311. }
  312. else
  313. {
  314. hr = E_UNEXPECTED; // Corrupted header information
  315. }
  316. }
  317. }
  318. }
  319. }
  320. if (FAILED(hr))
  321. {
  322. // reset the catalog header...
  323. m_rgHeader.wVersion = CATALOG_VERSION;
  324. m_rgHeader.cbSize = sizeof(m_rgHeader);
  325. SHGetThumbnailSizeForThumbsDB(&m_rgHeader.szThumbnailExtent);
  326. m_rgHeader.dwEntryCount = 0;
  327. }
  328. m_dwCatalogChange = 0;
  329. LeaveCriticalSection(&m_csInternals);
  330. pCatalog->Release();
  331. }
  332. }
  333. return hr;
  334. }
  335. HRESULT CThumbStore::SaveCatalog()
  336. {
  337. HRESULT hr = E_UNEXPECTED;
  338. if (_pStorageThumb)
  339. {
  340. _pStorageThumb->DestroyElement(CATALOG_STREAM);
  341. IStream *pCatalog;
  342. hr = _pStorageThumb->CreateStream(CATALOG_STREAM, GetAccessMode(STGM_WRITE, TRUE), NULL, NULL, &pCatalog);
  343. if (SUCCEEDED(hr))
  344. {
  345. EnterCriticalSection(&m_csInternals);
  346. // now write the catalog to the stream ...
  347. hr = IStream_Write(pCatalog, &m_rgHeader, sizeof(m_rgHeader));
  348. if (SUCCEEDED(hr))
  349. {
  350. CLISTPOS pCur = m_rgCatalog.GetHeadPosition();
  351. while (pCur && SUCCEEDED(hr))
  352. {
  353. CATALOG_ENTRY *pEntry = m_rgCatalog.GetNext(pCur);
  354. if (pEntry)
  355. {
  356. hr = IStream_Write(pCatalog, pEntry, pEntry->cbSize);
  357. }
  358. }
  359. }
  360. if (SUCCEEDED(hr))
  361. m_dwCatalogChange = 0;
  362. LeaveCriticalSection(&m_csInternals);
  363. pCatalog->Release();
  364. }
  365. }
  366. return hr;
  367. }
  368. void GenerateStreamName(LPWSTR pszBuffer, DWORD cchSize, DWORD dwNumber)
  369. {
  370. UINT cPos = 0;
  371. while ((dwNumber > 0) && (cPos < cchSize))
  372. {
  373. DWORD dwRem = dwNumber % 10;
  374. // based the fact that UNICODE chars 0-9 are the same as the ANSI chars 0 - 9
  375. pszBuffer[cPos++] = (WCHAR)(dwRem + '0');
  376. dwNumber /= 10;
  377. }
  378. pszBuffer[cPos] = 0;
  379. }
  380. // IPersist methods
  381. STDMETHODIMP CThumbStore::GetClassID(CLSID *pClsid)
  382. {
  383. *pClsid = CLSID_ShellThumbnailDiskCache;
  384. return S_OK;
  385. }
  386. // IPersistFolder
  387. STDMETHODIMP CThumbStore::Initialize(LPCITEMIDLIST pidl)
  388. {
  389. WCHAR szPath[MAX_PATH];
  390. HRESULT hr = E_UNEXPECTED;
  391. if (m_bCSInternalsInited && m_bCSLockInited)
  392. {
  393. hr = SHGetPathFromIDList(pidl, szPath) ? S_OK : E_FAIL;
  394. if (SUCCEEDED(hr))
  395. {
  396. if (PathAppend(szPath, THUMB_FILENAME))
  397. hr = _InitFromPath(szPath, STGM_READWRITE);
  398. else
  399. hr = E_INVALIDARG;
  400. }
  401. }
  402. return hr;
  403. }
  404. // IPersistFile
  405. STDMETHODIMP CThumbStore::IsDirty(void)
  406. {
  407. return m_dwCatalogChange ? S_OK : S_FALSE;
  408. }
  409. HRESULT CThumbStore::_InitFromPath(LPCTSTR pszPath, DWORD dwMode)
  410. {
  411. if (PathIsRemovable(pszPath))
  412. dwMode = STGM_READ;
  413. m_dwModeAllow = dwMode;
  414. return StringCchCopyW(m_szPath, ARRAYSIZE(m_szPath), pszPath);
  415. }
  416. STDMETHODIMP CThumbStore::Load(LPCWSTR pszFileName, DWORD dwMode)
  417. {
  418. TCHAR szPath[MAX_PATH];
  419. HRESULT hr = E_UNEXPECTED;
  420. if (PathCombine(szPath, pszFileName, THUMB_FILENAME))
  421. {
  422. hr = _InitFromPath(szPath, dwMode);
  423. }
  424. return hr;
  425. }
  426. STDMETHODIMP CThumbStore::Save(LPCWSTR pszFileName, BOOL fRemember)
  427. {
  428. return E_NOTIMPL;
  429. }
  430. STDMETHODIMP CThumbStore::SaveCompleted(LPCWSTR pszFileName)
  431. {
  432. return E_NOTIMPL;
  433. }
  434. STDMETHODIMP CThumbStore::GetCurFile(LPWSTR *ppszFileName)
  435. {
  436. return SHStrDupW(m_szPath, ppszFileName);
  437. }
  438. // IShellImageStore methods
  439. void CThumbStore::_SetAttribs(BOOL bForce)
  440. {
  441. // reduce spurious changenotifies by checking file attribs first
  442. DWORD dwAttrib = GetFileAttributes(m_szPath);
  443. if (bForce ||
  444. ((dwAttrib != 0xFFFFFFFF) &&
  445. (dwAttrib & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)))
  446. {
  447. SetFileAttributes(m_szPath, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
  448. WCHAR szStream[MAX_PATH];
  449. if (SUCCEEDED(StringCchCopyW(szStream, ARRAYSIZE(szStream), m_szPath)))
  450. {
  451. if (SUCCEEDED(StringCchCatW(szStream, ARRAYSIZE(szStream), TEXT(":encryptable"))))
  452. {
  453. HANDLE hStream = CreateFile(szStream, GENERIC_WRITE, NULL, NULL, CREATE_NEW, NULL, NULL);
  454. if (hStream != INVALID_HANDLE_VALUE)
  455. {
  456. CloseHandle(hStream);
  457. }
  458. }
  459. }
  460. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, m_szPath, NULL); // suppress the update dir
  461. }
  462. }
  463. STDMETHODIMP CThumbStore::Open(DWORD dwMode, DWORD *pdwLock)
  464. {
  465. if (m_szPath[0] == 0)
  466. {
  467. return E_UNEXPECTED;
  468. }
  469. if ((m_dwModeAllow == STGM_READ) && (dwMode != STGM_READ))
  470. return STG_E_ACCESSDENIED;
  471. // at this point we have the lock if we need it, so we can close and reopen if we
  472. // don't have it open with the right permissions...
  473. if (_pStorageThumb)
  474. {
  475. if (_dwModeStorage == dwMode)
  476. {
  477. // we already have it open in this mode...
  478. *pdwLock = AcquireLock();
  479. return S_FALSE;
  480. }
  481. else
  482. {
  483. // we are open and the mode is different, so close it. Note, no lock is passed, we already
  484. // have it
  485. HRESULT hr = Close(NULL);
  486. if (FAILED(hr))
  487. {
  488. return hr;
  489. }
  490. }
  491. }
  492. DWORD dwLock = AcquireLock();
  493. DWORD dwFlags = GetAccessMode(dwMode, FALSE);
  494. // now open the DocFile
  495. HRESULT hr = StgOpenStorage(m_szPath, NULL, dwFlags, NULL, NULL, &_pStorageThumb);
  496. if (SUCCEEDED(hr))
  497. {
  498. _dwModeStorage = dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE);
  499. _SetAttribs(FALSE);
  500. hr = LoadCatalog();
  501. *pdwLock = dwLock;
  502. }
  503. if (STG_E_DOCFILECORRUPT == hr)
  504. {
  505. DeleteFile(m_szPath);
  506. }
  507. if (FAILED(hr))
  508. {
  509. ReleaseLock(dwLock);
  510. }
  511. return hr;
  512. }
  513. STDMETHODIMP CThumbStore::Create(DWORD dwMode, DWORD *pdwLock)
  514. {
  515. if (m_szPath[0] == 0)
  516. {
  517. return E_UNEXPECTED;
  518. }
  519. if (_pStorageThumb)
  520. {
  521. // we already have it open, so we can't create it ...
  522. return STG_E_ACCESSDENIED;
  523. }
  524. if ((m_dwModeAllow == STGM_READ) && (dwMode != STGM_READ))
  525. return STG_E_ACCESSDENIED;
  526. DWORD dwLock = AcquireLock();
  527. DWORD dwFlags = GetAccessMode(dwMode, FALSE);
  528. HRESULT hr = StgCreateDocfile(m_szPath, dwFlags, NULL, &_pStorageThumb);
  529. if (SUCCEEDED(hr))
  530. {
  531. _dwModeStorage = dwMode & (STGM_READ | STGM_WRITE | STGM_READWRITE);
  532. _SetAttribs(FALSE);
  533. *pdwLock = dwLock;
  534. }
  535. if (FAILED(hr))
  536. {
  537. ReleaseLock(dwLock);
  538. }
  539. return hr;
  540. }
  541. STDMETHODIMP CThumbStore::ReleaseLock(DWORD const *pdwLock)
  542. {
  543. ReleaseLock(*pdwLock);
  544. return S_OK;
  545. }
  546. STDMETHODIMP CThumbStore::IsLocked()
  547. {
  548. return (m_fLocked > 0 ? S_OK : S_FALSE);
  549. }
  550. // pdwLock can be NULL indicating close the last opened lock
  551. STDMETHODIMP CThumbStore::Close(DWORD const *pdwLock)
  552. {
  553. DWORD dwLock;
  554. DWORD const *pdwRel = pdwLock;
  555. if (!pdwLock)
  556. {
  557. dwLock = AcquireLock();
  558. pdwRel = &dwLock;
  559. }
  560. HRESULT hr = S_FALSE;
  561. if (_pStorageThumb)
  562. {
  563. if (_dwModeStorage != STGM_READ)
  564. {
  565. // write out the new catalog...
  566. hr = Commit(NULL);
  567. _pStorageThumb->Commit(0);
  568. SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, m_szPath, NULL); // suppress the update dir
  569. }
  570. _pStorageThumb->Release();
  571. _pStorageThumb = NULL;
  572. }
  573. ReleaseLock(*pdwRel);
  574. return hr;
  575. }
  576. // pdwLock can be NULL meaning use the current lock
  577. STDMETHODIMP CThumbStore::Commit(DWORD const *pdwLock)
  578. {
  579. DWORD dwLock;
  580. if (!pdwLock)
  581. {
  582. dwLock = AcquireLock();
  583. pdwLock = &dwLock;
  584. }
  585. HRESULT hr = S_FALSE;
  586. if (_pStorageThumb && _dwModeStorage != STGM_READ)
  587. {
  588. if (m_dwCatalogChange)
  589. {
  590. SaveCatalog();
  591. }
  592. hr = S_OK;
  593. }
  594. ReleaseLock(*pdwLock);
  595. return hr;
  596. }
  597. STDMETHODIMP CThumbStore::GetMode(DWORD *pdwMode)
  598. {
  599. if (!pdwMode)
  600. {
  601. return E_INVALIDARG;
  602. }
  603. if (_pStorageThumb)
  604. {
  605. *pdwMode = _dwModeStorage;
  606. return S_OK;
  607. }
  608. *pdwMode = 0;
  609. return S_FALSE;
  610. }
  611. STDMETHODIMP CThumbStore::GetCapabilities(DWORD *pdwMode)
  612. {
  613. ASSERT(pdwMode);
  614. // right now, both are needed/supported for thumbs.db
  615. *pdwMode = SHIMSTCAPFLAG_LOCKABLE | SHIMSTCAPFLAG_PURGEABLE;
  616. return S_OK;
  617. }
  618. STDMETHODIMP CThumbStore::AddEntry(LPCWSTR pszName, const FILETIME *pftTimeStamp, DWORD dwMode, HBITMAP hImage)
  619. {
  620. ASSERT(pszName);
  621. if (!_pStorageThumb)
  622. {
  623. return E_UNEXPECTED;
  624. }
  625. if (_dwModeStorage == STGM_READ)
  626. {
  627. // can't modify in this mode...
  628. return E_ACCESSDENIED;
  629. }
  630. // this will block unless we already have the lock on this thread...
  631. DWORD dwLock = AcquireLock();
  632. DWORD dwStream = 0;
  633. CLISTPOS pCur = NULL;
  634. CATALOG_ENTRY *pNode = NULL;
  635. EnterCriticalSection(&m_csInternals);
  636. if (FindStreamID(pszName, &dwStream, &pNode) != S_OK)
  637. {
  638. // needs adding to the catalog...
  639. UINT cchName = lstrlenW(pszName) + 1;
  640. UINT cbSize = sizeof(*pNode) + (cchName - 1) * sizeof(WCHAR); // subtract 1 since *pNode has a WCHAR[1]
  641. pNode = (CATALOG_ENTRY *)LocalAlloc(LPTR, cbSize);
  642. if (pNode == NULL)
  643. {
  644. LeaveCriticalSection(&m_csInternals);
  645. ReleaseLock(dwLock);
  646. return E_OUTOFMEMORY;
  647. }
  648. pNode->cbSize = cbSize;
  649. if (pftTimeStamp)
  650. {
  651. pNode->ftTimeStamp = *pftTimeStamp;
  652. }
  653. dwStream = pNode->dwIndex = ++m_dwMaxIndex;
  654. if (SUCCEEDED(StringCchCopyW(pNode->szName, cchName, pszName)))
  655. {
  656. pCur = m_rgCatalog.AddTail(pNode);
  657. }
  658. if (pCur == NULL)
  659. {
  660. LocalFree(pNode);
  661. LeaveCriticalSection(&m_csInternals);
  662. ReleaseLock(dwLock);
  663. return E_OUTOFMEMORY;
  664. }
  665. m_rgHeader.dwEntryCount++;
  666. }
  667. else if (pftTimeStamp)
  668. {
  669. // update the timestamp .....
  670. pNode->ftTimeStamp = *pftTimeStamp;
  671. }
  672. LeaveCriticalSection(&m_csInternals);
  673. IStream *pStream = NULL;
  674. HRESULT hr = THR(GetEntryStream(dwStream, dwMode, &pStream));
  675. if (SUCCEEDED(hr))
  676. {
  677. hr = THR(WriteImage(pStream, hImage));
  678. pStream->Release();
  679. }
  680. if (FAILED(hr) && pCur)
  681. {
  682. // take it back out of the list if we added it...
  683. EnterCriticalSection(&m_csInternals);
  684. m_rgCatalog.RemoveAt(pCur);
  685. m_rgHeader.dwEntryCount--;
  686. LeaveCriticalSection(&m_csInternals);
  687. LocalFree(pNode);
  688. }
  689. if (SUCCEEDED(hr))
  690. {
  691. // catalog change....
  692. m_dwCatalogChange++;
  693. }
  694. ReleaseLock(dwLock);
  695. return hr;
  696. }
  697. STDMETHODIMP CThumbStore::GetEntry(LPCWSTR pszName, DWORD dwMode, HBITMAP *phImage)
  698. {
  699. if (!_pStorageThumb)
  700. {
  701. return E_UNEXPECTED;
  702. }
  703. HRESULT hr;
  704. DWORD dwStream;
  705. if (FindStreamID(pszName, &dwStream, NULL) != S_OK)
  706. {
  707. hr = E_FAIL;
  708. }
  709. else
  710. {
  711. IStream *pStream;
  712. hr = GetEntryStream(dwStream, dwMode, &pStream);
  713. if (SUCCEEDED(hr))
  714. {
  715. hr = ReadImage(pStream, phImage);
  716. pStream->Release();
  717. }
  718. }
  719. return hr;
  720. }
  721. BOOL CThumbStore::_MatchNodeName(CATALOG_ENTRY *pNode, LPCWSTR pszName)
  722. {
  723. return (StrCmpIW(pNode->szName, pszName) == 0) ||
  724. (StrCmpIW(PathFindFileName(pNode->szName), pszName) == 0); // match old thumbs.db files
  725. }
  726. STDMETHODIMP CThumbStore::DeleteEntry(LPCWSTR pszName)
  727. {
  728. if (!_pStorageThumb)
  729. {
  730. return E_UNEXPECTED;
  731. }
  732. if (_dwModeStorage == STGM_READ)
  733. {
  734. // can't modify in this mode...
  735. return E_ACCESSDENIED;
  736. }
  737. DWORD dwLock = AcquireLock();
  738. EnterCriticalSection(&m_csInternals);
  739. // check to see if it already exists.....
  740. CATALOG_ENTRY *pNode = NULL;
  741. CLISTPOS pCur = m_rgCatalog.GetHeadPosition();
  742. while (pCur != NULL)
  743. {
  744. CLISTPOS pDel = pCur;
  745. pNode = m_rgCatalog.GetNext(pCur);
  746. ASSERT(pNode != NULL);
  747. if (_MatchNodeName(pNode, pszName))
  748. {
  749. m_rgCatalog.RemoveAt(pDel);
  750. m_rgHeader.dwEntryCount--;
  751. m_dwCatalogChange++;
  752. if (pNode->dwIndex == m_dwMaxIndex)
  753. {
  754. m_dwMaxIndex--;
  755. }
  756. LeaveCriticalSection(&m_csInternals);
  757. WCHAR szStream[30];
  758. GenerateStreamName(szStream, ARRAYSIZE(szStream), pNode->dwIndex);
  759. _pStorageThumb->DestroyElement(szStream);
  760. LocalFree(pNode);
  761. ReleaseLock(dwLock);
  762. return S_OK;
  763. }
  764. }
  765. LeaveCriticalSection(&m_csInternals);
  766. ReleaseLock(dwLock);
  767. return E_INVALIDARG;
  768. }
  769. STDMETHODIMP CThumbStore::IsEntryInStore(LPCWSTR pszName, FILETIME *pftTimeStamp)
  770. {
  771. if (!_pStorageThumb)
  772. {
  773. return E_UNEXPECTED;
  774. }
  775. DWORD dwStream = 0;
  776. CATALOG_ENTRY *pNode = NULL;
  777. EnterCriticalSection(&m_csInternals);
  778. HRESULT hr = FindStreamID(pszName, &dwStream, &pNode);
  779. if (pftTimeStamp && SUCCEEDED(hr))
  780. {
  781. ASSERT(pNode);
  782. *pftTimeStamp = pNode->ftTimeStamp;
  783. }
  784. LeaveCriticalSection(&m_csInternals);
  785. return (hr == S_OK) ? S_OK : S_FALSE;
  786. }
  787. STDMETHODIMP CThumbStore::Enum(IEnumShellImageStore **ppEnum)
  788. {
  789. return CEnumThumbStore_Create(this, ppEnum);
  790. }
  791. HRESULT CThumbStore::FindStreamID(LPCWSTR pszName, DWORD *pdwStream, CATALOG_ENTRY ** ppNode)
  792. {
  793. // check to see if it already exists in the catalog.....
  794. CATALOG_ENTRY *pNode = NULL;
  795. CLISTPOS pCur = m_rgCatalog.GetHeadPosition();
  796. while (pCur != NULL)
  797. {
  798. pNode = m_rgCatalog.GetNext(pCur);
  799. ASSERT(pNode != NULL);
  800. if (_MatchNodeName(pNode, pszName))
  801. {
  802. *pdwStream = pNode->dwIndex;
  803. if (ppNode != NULL)
  804. {
  805. *ppNode = pNode;
  806. }
  807. return S_OK;
  808. }
  809. }
  810. return E_FAIL;
  811. }
  812. CEnumThumbStore::CEnumThumbStore()
  813. {
  814. m_pStore = NULL;
  815. m_pPos = 0;
  816. m_dwCatalogChange = 0;
  817. }
  818. CEnumThumbStore::~CEnumThumbStore()
  819. {
  820. if (m_pStore)
  821. {
  822. SAFECAST(m_pStore, IPersistFile *)->Release();
  823. }
  824. }
  825. STDMETHODIMP CEnumThumbStore::Reset(void)
  826. {
  827. m_pPos = m_pStore->m_rgCatalog.GetHeadPosition();
  828. m_dwCatalogChange = m_pStore->m_dwCatalogChange;
  829. return S_OK;
  830. }
  831. STDMETHODIMP CEnumThumbStore::Next(ULONG celt, PENUMSHELLIMAGESTOREDATA * prgElt, ULONG * pceltFetched)
  832. {
  833. if ((celt > 1 && !pceltFetched) || !celt)
  834. {
  835. return E_INVALIDARG;
  836. }
  837. if (m_dwCatalogChange != m_pStore->m_dwCatalogChange)
  838. {
  839. return E_UNEXPECTED;
  840. }
  841. ULONG celtFetched = 0;
  842. HRESULT hr = S_OK;
  843. while (celtFetched < celt && m_pPos)
  844. {
  845. CThumbStore::CATALOG_ENTRY *pNode = m_pStore->m_rgCatalog.GetNext(m_pPos);
  846. ASSERT(pNode);
  847. PENUMSHELLIMAGESTOREDATA pElt = (PENUMSHELLIMAGESTOREDATA) CoTaskMemAlloc(sizeof(ENUMSHELLIMAGESTOREDATA));
  848. if (!pElt)
  849. {
  850. hr = E_OUTOFMEMORY;
  851. break;
  852. }
  853. hr = StringCchCopyW(pElt->szPath, ARRAYSIZE(pElt->szPath), pNode->szName);
  854. if (FAILED(hr))
  855. {
  856. CoTaskMemFree(pElt);
  857. break;
  858. }
  859. pElt->ftTimeStamp = pNode->ftTimeStamp;
  860. prgElt[celtFetched] = pElt;
  861. celtFetched++;
  862. }
  863. if (FAILED(hr) && celtFetched)
  864. {
  865. // cleanup
  866. for (ULONG celtCleanup = 0; celtCleanup < celtFetched; celtCleanup++)
  867. {
  868. CoTaskMemFree(prgElt[celtCleanup]);
  869. prgElt[celtCleanup] = NULL;
  870. }
  871. celtFetched = 0;
  872. }
  873. if (pceltFetched)
  874. {
  875. *pceltFetched = celtFetched;
  876. }
  877. if (FAILED(hr))
  878. return hr;
  879. if (!celtFetched)
  880. return E_FAIL;
  881. return (celtFetched < celt) ? S_FALSE : S_OK;
  882. }
  883. STDMETHODIMP CEnumThumbStore::Skip(ULONG celt)
  884. {
  885. if (!celt)
  886. {
  887. return E_INVALIDARG;
  888. }
  889. if (m_dwCatalogChange != m_pStore->m_dwCatalogChange)
  890. {
  891. return E_UNEXPECTED;
  892. }
  893. ULONG celtSkipped = 0;
  894. while (celtSkipped < celt &&m_pPos)
  895. {
  896. m_pStore->m_rgCatalog.GetNext(m_pPos);
  897. }
  898. if (!celtSkipped)
  899. {
  900. return E_FAIL;
  901. }
  902. return (celtSkipped < celt) ? S_FALSE : S_OK;
  903. }
  904. STDMETHODIMP CEnumThumbStore::Clone(IEnumShellImageStore ** ppEnum)
  905. {
  906. CEnumThumbStore * pEnum = new CComObject<CEnumThumbStore>;
  907. if (!pEnum)
  908. {
  909. return E_OUTOFMEMORY;
  910. }
  911. ((IPersistFile *)m_pStore)->AddRef();
  912. pEnum->m_pStore = m_pStore;
  913. pEnum->m_dwCatalogChange = m_dwCatalogChange;
  914. // created with zero ref count....
  915. pEnum->AddRef();
  916. *ppEnum = SAFECAST(pEnum, IEnumShellImageStore *);
  917. return S_OK;
  918. }
  919. HRESULT CEnumThumbStore_Create(CThumbStore * pThis, IEnumShellImageStore ** ppEnum)
  920. {
  921. CEnumThumbStore * pEnum = new CComObject<CEnumThumbStore>;
  922. if (!pEnum)
  923. {
  924. return E_OUTOFMEMORY;
  925. }
  926. ((IPersistFile *)pThis)->AddRef();
  927. pEnum->m_pStore = pThis;
  928. // created with zero ref count....
  929. pEnum->AddRef();
  930. *ppEnum = SAFECAST(pEnum, IEnumShellImageStore *);
  931. return S_OK;
  932. }
  933. HRESULT Version1ReadImage(IStream *pStream, DWORD cbSize, HBITMAP *phImage)
  934. {
  935. *phImage = NULL;
  936. HRESULT hr = E_OUTOFMEMORY;
  937. BITMAPINFO *pbi = (BITMAPINFO *) LocalAlloc(LPTR, cbSize);
  938. if (pbi)
  939. {
  940. hr = IStream_Read(pStream, pbi, cbSize);
  941. if (SUCCEEDED(hr))
  942. {
  943. HDC hdc = GetDC(NULL);
  944. if (hdc)
  945. {
  946. *phImage = CreateDIBitmap(hdc, &(pbi->bmiHeader), CBM_INIT, CalcBitsOffsetInDIB(pbi), pbi, DIB_RGB_COLORS);
  947. ReleaseDC(NULL, hdc);
  948. hr = S_OK;
  949. }
  950. }
  951. LocalFree(pbi);
  952. }
  953. return hr;
  954. }
  955. HRESULT CThumbStore::ReadImage(IStream *pStream, HBITMAP *phImage)
  956. {
  957. STREAM_HEADER rgHead;
  958. HRESULT hr = IStream_Read(pStream, &rgHead, sizeof(rgHead));
  959. if (SUCCEEDED(hr))
  960. {
  961. if (rgHead.cbSize == sizeof(rgHead))
  962. {
  963. if (rgHead.dwFlags == STREAMFLAGS_DIB)
  964. {
  965. hr = Version1ReadImage(pStream, rgHead.ulSize, phImage);
  966. }
  967. else if (rgHead.dwFlags == STREAMFLAGS_JPEG)
  968. {
  969. // gdi+ will only accept the jpg stream if it's at the start of the
  970. // stream. We copy the jpeg into its own stream.
  971. IStream *pstmMem;
  972. hr = CreateStreamOnHGlobal(NULL, TRUE, &pstmMem);
  973. if (SUCCEEDED(hr))
  974. {
  975. ULARGE_INTEGER ulSize = { rgHead.ulSize };
  976. hr = pStream->CopyTo(pstmMem, ulSize, NULL, NULL);
  977. if (SUCCEEDED(hr))
  978. {
  979. LARGE_INTEGER liSeek = {0, 0};
  980. hr = pstmMem->Seek(liSeek, STREAM_SEEK_SET, NULL);
  981. if (SUCCEEDED(hr))
  982. {
  983. hr = DecompressImage(pstmMem, phImage);
  984. }
  985. }
  986. pstmMem->Release();
  987. }
  988. }
  989. else
  990. {
  991. hr = E_FAIL;
  992. }
  993. }
  994. else
  995. {
  996. hr = E_FAIL;
  997. }
  998. }
  999. return hr;
  1000. }
  1001. HRESULT CThumbStore::WriteImage(IStream *pStream, HBITMAP hImage)
  1002. {
  1003. STREAM_HEADER rgHead;
  1004. // skip past the header. It will be writen when we know the image size.
  1005. LARGE_INTEGER liSeek = { sizeof(rgHead) };
  1006. HRESULT hr = pStream->Seek(liSeek, STREAM_SEEK_SET, NULL);
  1007. if (SUCCEEDED(hr))
  1008. {
  1009. hr = CompressImage(pStream, hImage);
  1010. if (SUCCEEDED(hr))
  1011. {
  1012. STATSTG stat;
  1013. hr = pStream->Stat(&stat, STATFLAG_NONAME);
  1014. if (SUCCEEDED(hr))
  1015. {
  1016. //now write the header
  1017. rgHead.cbSize = sizeof(rgHead);
  1018. rgHead.dwFlags = STREAMFLAGS_JPEG;
  1019. rgHead.ulSize = stat.cbSize.QuadPart - sizeof(rgHead);
  1020. //move to the beginning of the stream to write the header
  1021. liSeek.QuadPart = 0;
  1022. hr = pStream->Seek(liSeek, STREAM_SEEK_SET, NULL);
  1023. if (SUCCEEDED(hr))
  1024. {
  1025. hr = IStream_Write(pStream, &rgHead, sizeof(rgHead));
  1026. }
  1027. }
  1028. }
  1029. }
  1030. return hr;
  1031. }
  1032. HRESULT CThumbStore::GetEntryStream(DWORD dwStream, DWORD dwMode, IStream **ppStream)
  1033. {
  1034. WCHAR szStream[30];
  1035. GenerateStreamName(szStream, ARRAYSIZE(szStream), dwStream);
  1036. // leave only the STG_READ | STGM_READWRITE | STGM_WRITE modes
  1037. dwMode &= STGM_READ | STGM_WRITE | STGM_READWRITE;
  1038. if (!_pStorageThumb)
  1039. {
  1040. return E_UNEXPECTED;
  1041. }
  1042. if (_dwModeStorage != STGM_READWRITE && dwMode != _dwModeStorage)
  1043. {
  1044. return E_ACCESSDENIED;
  1045. }
  1046. DWORD dwFlags = GetAccessMode(dwMode, TRUE);
  1047. if (dwFlags & STGM_WRITE)
  1048. {
  1049. _pStorageThumb->DestroyElement(szStream);
  1050. return _pStorageThumb->CreateStream(szStream, dwFlags, NULL, NULL, ppStream);
  1051. }
  1052. else
  1053. {
  1054. return _pStorageThumb->OpenStream(szStream, NULL, dwFlags, NULL, ppStream);
  1055. }
  1056. }
  1057. DWORD CThumbStore::GetAccessMode(DWORD dwMode, BOOL fStream)
  1058. {
  1059. dwMode &= STGM_READ | STGM_WRITE | STGM_READWRITE;
  1060. DWORD dwFlags = dwMode;
  1061. // the root only needs Deny_Write, streams need exclusive....
  1062. if (dwMode == STGM_READ && !fStream)
  1063. {
  1064. dwFlags |= STGM_SHARE_DENY_WRITE;
  1065. }
  1066. else
  1067. {
  1068. dwFlags |= STGM_SHARE_EXCLUSIVE;
  1069. }
  1070. return dwFlags;
  1071. }
  1072. const CLSID CLSID_JPEGCodec = { 0x557cf401, 0x1a04, 0x11d3, {0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e}};
  1073. HRESULT CThumbStore::CompressImage(IStream *pStream, HBITMAP hBmp)
  1074. {
  1075. HRESULT hr = E_FAIL;
  1076. // create the gdi+ bitmap
  1077. Bitmap* pBitmap = new Bitmap(hBmp, NULL);
  1078. if (pBitmap)
  1079. {
  1080. // Set the JPG Quailty
  1081. EncoderParameters ep;
  1082. ep.Parameter[0].Guid = EncoderQuality;
  1083. ep.Parameter[0].Type = EncoderParameterValueTypeLong;
  1084. ep.Parameter[0].NumberOfValues = 1;
  1085. ep.Parameter[0].Value = &m_iThumbnailQuality;
  1086. ep.Count = 1;
  1087. // Save the Bitmap to the stream in JPG format
  1088. hr = HR_FROM_STATUS(pBitmap->Save(pStream, &CLSID_JPEGCodec, &ep));
  1089. delete pBitmap;
  1090. }
  1091. return hr;
  1092. }
  1093. HRESULT CThumbStore::DecompressImage(IStream *pStream, HBITMAP *phBmp)
  1094. {
  1095. HRESULT hr = E_FAIL;
  1096. // Create gdi+ Bitmap from image stream
  1097. Bitmap* pBitmap = new Bitmap(pStream, true);
  1098. if (pBitmap)
  1099. {
  1100. hr = HR_FROM_STATUS(pBitmap->GetHBITMAP(Color::Black, phBmp));
  1101. delete pBitmap;
  1102. }
  1103. return hr;
  1104. }
  1105. HRESULT DeleteFileThumbnail(LPCWSTR szFilePath)
  1106. {
  1107. WCHAR szFolder[MAX_PATH];
  1108. WCHAR *szFile;
  1109. HRESULT hr = E_FAIL;
  1110. hr = StringCchCopyW(szFolder, ARRAYSIZE(szFolder), szFilePath);
  1111. if (SUCCEEDED(hr))
  1112. {
  1113. szFile = PathFindFileName(szFolder);
  1114. if (szFile != szFolder)
  1115. {
  1116. *(szFile - 1) = 0; // NULL terminates folder
  1117. IShellImageStore *pDiskCache = NULL;
  1118. hr = LoadFromFile(CLSID_ShellThumbnailDiskCache, szFolder, IID_PPV_ARG(IShellImageStore, &pDiskCache));
  1119. if (SUCCEEDED(hr))
  1120. {
  1121. IPersistFile *pPersist = NULL;
  1122. hr = pDiskCache->QueryInterface(IID_PPV_ARG(IPersistFile, &pPersist));
  1123. if (SUCCEEDED(hr))
  1124. {
  1125. hr = pPersist->Load(szFolder, STGM_READWRITE);
  1126. if (SUCCEEDED(hr))
  1127. {
  1128. DWORD dwLock;
  1129. hr = pDiskCache->Open(STGM_READWRITE, &dwLock);
  1130. if (SUCCEEDED(hr))
  1131. {
  1132. hr = pDiskCache->DeleteEntry(szFile);
  1133. pDiskCache->Close(&dwLock);
  1134. }
  1135. }
  1136. pPersist->Release();
  1137. }
  1138. pDiskCache->Release();
  1139. }
  1140. }
  1141. else
  1142. {
  1143. hr = E_FAIL;
  1144. }
  1145. }
  1146. return hr;
  1147. }