#include "priv.h" #include "sccls.h" #include "runtask.h" #include "legacy.h" #include // defines some values used for fmtid and pid #define DEFINE_SCID(name, fmtid, pid) const SHCOLUMNID name = { fmtid, pid } DEFINE_SCID(SCID_WRITETIME, PSGUID_STORAGE, PID_STG_WRITETIME); class CThumbnail : public IThumbnail2, public CLogoBase { public: CThumbnail(void); // IUnknown STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj); // IThumbnail STDMETHODIMP Init(HWND hwnd, UINT uMsg); STDMETHODIMP GetBitmap(LPCWSTR pszFile, DWORD dwItem, LONG lWidth, LONG lHeight); // IThumbnail2 STDMETHODIMP GetBitmapFromIDList(LPCITEMIDLIST pidl, DWORD dwItem, LONG lWidth, LONG lHeight); private: ~CThumbnail(void); LONG _cRef; HWND _hwnd; UINT _uMsg; IShellImageStore *_pImageStore; virtual IShellFolder * GetSF() {ASSERT(0);return NULL;}; virtual HWND GetHWND() {ASSERT(0); return _hwnd;}; HRESULT UpdateLogoCallback(DWORD dwItem, int iIcon, HBITMAP hImage, LPCWSTR pszCache, BOOL fCache); REFTASKOWNERID GetTOID(); BOOL _InCache(LPCWSTR pszItemPath, LPCWSTR pszGLocation, const FILETIME * pftDateStamp); HRESULT _BitmapFromIDList(LPCITEMIDLIST pidl, LPCWSTR pszFile, DWORD dwItem, LONG lWidth, LONG lHeight); HRESULT _InitTaskCancelItems(); }; static const GUID TOID_Thumbnail = { 0xadec3450, 0xe907, 0x11d0, {0xa5, 0x7b, 0x00, 0xc0, 0x4f, 0xc2, 0xf7, 0x6a} }; HRESULT CDiskCacheTask_Create(CLogoBase * pView, IShellImageStore *pImageStore, LPCWSTR pszItem, LPCWSTR pszGLocation, DWORD dwItem, IRunnableTask ** ppTask); class CDiskCacheTask : public CRunnableTask { public: CDiskCacheTask(); STDMETHODIMP RunInitRT(void); friend HRESULT CDiskCacheTask_Create(CLogoBase * pView, IShellImageStore *pImageStore, LPCWSTR pszItem, LPCWSTR pszGLocation, DWORD dwItem, const SIZE * prgSize, IRunnableTask ** ppTask); private: ~CDiskCacheTask(); HRESULT PrepImage(HBITMAP * phBmp); IShellImageStore *_pImageStore; WCHAR _szItem[MAX_PATH]; WCHAR _szGLocation[MAX_PATH]; CLogoBase * _pView; DWORD _dwItem; SIZE m_rgSize; }; // CreateInstance HRESULT CThumbnail_CreateInstance(IUnknown *punkOuter, IUnknown **ppunk, LPCOBJECTINFO poi) { *ppunk = NULL; CThumbnail *pthumbnail = new CThumbnail(); if (pthumbnail) { *ppunk = SAFECAST(pthumbnail, IThumbnail*); return S_OK; } return E_OUTOFMEMORY; } // Constructor / Destructor CThumbnail::CThumbnail(void) : _cRef(1) { DllAddRef(); } CThumbnail::~CThumbnail(void) { if (_pTaskScheduler) { _pTaskScheduler->RemoveTasks(TOID_Thumbnail, ITSAT_DEFAULT_LPARAM, FALSE); _pTaskScheduler->Release(); _pTaskScheduler = NULL; } DllRelease(); } HRESULT CThumbnail::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CThumbnail, IThumbnail2), QITABENTMULTI(CThumbnail, IThumbnail, IThumbnail2), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } ULONG CThumbnail::AddRef(void) { return InterlockedIncrement(&_cRef); } ULONG CThumbnail::Release(void) { if (InterlockedDecrement(&_cRef) > 0) return _cRef; delete this; return 0; } // IThumbnail HRESULT CThumbnail::Init(HWND hwnd, UINT uMsg) { _hwnd = hwnd; _uMsg = uMsg; ASSERT(NULL == _pTaskScheduler); return S_OK; } HRESULT CThumbnail::_InitTaskCancelItems() { if (!_pTaskScheduler) { if (SUCCEEDED(CoCreateInstance(CLSID_ShellTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellTaskScheduler, &_pTaskScheduler)))) { // make sure RemoveTasks() actually kills old tasks even if they're not done yet _pTaskScheduler->Status(ITSSFLAG_KILL_ON_DESTROY, ITSS_THREAD_TIMEOUT_NO_CHANGE); } } if (_pTaskScheduler) { // Kill any old tasks in the scheduler. _pTaskScheduler->RemoveTasks(TOID_Thumbnail, ITSAT_DEFAULT_LPARAM, FALSE); } return _pTaskScheduler ? S_OK : E_FAIL; } HRESULT CThumbnail::_BitmapFromIDList(LPCITEMIDLIST pidl, LPCWSTR pszFile, DWORD dwItem, LONG lWidth, LONG lHeight) { LPCITEMIDLIST pidlLast; IShellFolder *psf; HRESULT hr = SHBindToIDListParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlLast); if (SUCCEEDED(hr)) { IExtractImage *pei; hr = psf->GetUIObjectOf(NULL, 1, &pidlLast, IID_PPV_ARG_NULL(IExtractImage, &pei)); if (SUCCEEDED(hr)) { DWORD dwPriority; DWORD dwFlags = IEIFLAG_ASYNC | IEIFLAG_SCREEN | IEIFLAG_OFFLINE; SIZEL rgSize = {lWidth, lHeight}; WCHAR szBufferW[MAX_PATH]; hr = pei->GetLocation(szBufferW, ARRAYSIZE(szBufferW), &dwPriority, &rgSize, SHGetCurColorRes(), &dwFlags); if (SUCCEEDED(hr)) { if (S_OK == hr) { HBITMAP hBitmap; hr = pei->Extract(&hBitmap); if (SUCCEEDED(hr)) { hr = UpdateLogoCallback(dwItem, 0, hBitmap, NULL, TRUE); } } else hr = E_FAIL; } else if (E_PENDING == hr) { WCHAR szPath[MAX_PATH]; if (NULL == pszFile) { DisplayNameOf(psf, pidlLast, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath)); pszFile = szPath; } // now get the date stamp and check the disk cache.... FILETIME ftImageTimeStamp; BOOL fNoDateStamp = TRUE; // try it in the background... // od they support date stamps.... IExtractImage2 *pei2; if (SUCCEEDED(pei->QueryInterface(IID_PPV_ARG(IExtractImage2, &pei2)))) { if (SUCCEEDED(pei2->GetDateStamp(&ftImageTimeStamp))) { fNoDateStamp = FALSE; // we have a date stamp.. } pei2->Release(); } else { IShellFolder2 *psf2; if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IShellFolder2, &psf2)))) { if (SUCCEEDED(GetDateProperty(psf2, pidlLast, &SCID_WRITETIME, &ftImageTimeStamp))) { fNoDateStamp = FALSE; // we have a date stamp.. } psf2->Release(); } } // if it is in the cache, and it is an uptodate image, then fetch from disk.... // if the timestamps are wrong, then the extract code further down will then try // we only test the cache on NT5 because the templates are old on other platforms and // thus the image will be the wrong size... IRunnableTask *prt; if (IsOS(OS_WIN2000ORGREATER) && _InCache(pszFile, szBufferW, (fNoDateStamp ? NULL : &ftImageTimeStamp))) { hr = CDiskCacheTask_Create(this, _pImageStore, pszFile, szBufferW, dwItem, &rgSize, &prt); if (SUCCEEDED(hr)) { // let go of the image store, so the task has the only ref and the lock.. ATOMICRELEASE(_pImageStore); } } else { // Cannot hold the prt which is returned in a member variable since that // would be a circular reference hr = CExtractImageTask_Create(this, pei, L"", dwItem, -1, EITF_SAVEBITMAP | EITF_ALWAYSCALL, &prt); } if (SUCCEEDED(hr)) { // Add the task to the scheduler. hr = _pTaskScheduler->AddTask(prt, TOID_Thumbnail, ITSAT_DEFAULT_LPARAM, dwPriority); prt->Release(); } } pei->Release(); } psf->Release(); } ATOMICRELEASE(_pImageStore); return hr; } STDMETHODIMP CThumbnail::GetBitmap(LPCWSTR pszFile, DWORD dwItem, LONG lWidth, LONG lHeight) { HRESULT hr = _InitTaskCancelItems(); if (pszFile) { LPITEMIDLIST pidl = ILCreateFromPathW(pszFile); if (pidl) { hr = _BitmapFromIDList(pidl, pszFile, dwItem, lWidth, lHeight); ILFree(pidl); } else hr = E_FAIL; } return hr; } // IThumbnail2 STDMETHODIMP CThumbnail::GetBitmapFromIDList(LPCITEMIDLIST pidl, DWORD dwItem, LONG lWidth, LONG lHeight) { HRESULT hr = _InitTaskCancelItems(); if (pidl) { hr = _BitmapFromIDList(pidl, NULL, dwItem, lWidth, lHeight); } return hr; } // private stuff HRESULT CThumbnail::UpdateLogoCallback(DWORD dwItem, int iIcon, HBITMAP hImage, LPCWSTR pszCache, BOOL fCache) { if (!PostMessage(_hwnd, _uMsg, dwItem, (LPARAM)hImage)) { DeleteObject(hImage); } return S_OK; } REFTASKOWNERID CThumbnail::GetTOID() { return TOID_Thumbnail; } BOOL CThumbnail::_InCache(LPCWSTR pszItemPath, LPCWSTR pszGLocation, const FILETIME * pftDateStamp) { BOOL fRes = FALSE; HRESULT hr; if (_pImageStore) hr = S_OK; else { // init the cache only once, assume all items from same folder! WCHAR szName[MAX_PATH]; StrCpyNW(szName, pszItemPath, ARRAYSIZE(szName)); PathRemoveFileSpecW(szName); hr = LoadFromFile(CLSID_ShellThumbnailDiskCache, szName, IID_PPV_ARG(IShellImageStore, &_pImageStore)); } if (SUCCEEDED(hr)) { DWORD dwStoreLock; hr = _pImageStore->Open(STGM_READ, &dwStoreLock); if (SUCCEEDED(hr)) { FILETIME ftCacheDateStamp; hr = _pImageStore->IsEntryInStore(pszGLocation, &ftCacheDateStamp); if ((hr == S_OK) && (!pftDateStamp || (pftDateStamp->dwLowDateTime == ftCacheDateStamp.dwLowDateTime && pftDateStamp->dwHighDateTime == ftCacheDateStamp.dwHighDateTime))) { fRes = TRUE; } _pImageStore->Close(&dwStoreLock); } } return fRes; } HRESULT CDiskCacheTask_Create(CLogoBase * pView, IShellImageStore *pImageStore, LPCWSTR pszItem, LPCWSTR pszGLocation, DWORD dwItem, const SIZE * prgSize, IRunnableTask ** ppTask) { CDiskCacheTask *pTask = new CDiskCacheTask; if (pTask == NULL) { return E_OUTOFMEMORY; } StrCpyW(pTask->_szItem, pszItem); StrCpyW(pTask->_szGLocation, pszGLocation); pTask->_pView = pView; pTask->_pImageStore = pImageStore; pImageStore->AddRef(); pTask->_dwItem = dwItem; pTask->m_rgSize = * prgSize; *ppTask = SAFECAST(pTask, IRunnableTask *); return S_OK; } STDMETHODIMP CDiskCacheTask::RunInitRT() { // otherwise, run the task .... HBITMAP hBmp = NULL; DWORD dwLock; HRESULT hr = _pImageStore->Open(STGM_READ, &dwLock); if (SUCCEEDED(hr)) { // at this point, we assume that it IS in the cache, and we already have a read lock on the cache... hr = _pImageStore->GetEntry(_szGLocation, STGM_READ, &hBmp); // release the lock, we don't need it... _pImageStore->Close(&dwLock); } ATOMICRELEASE(_pImageStore); if (hBmp) { PrepImage(&hBmp); _pView->UpdateLogoCallback(_dwItem, 0, hBmp, _szItem, TRUE); } // ensure we don't return the "we've suspended" value... if (hr == E_PENDING) hr = E_FAIL; return hr; } CDiskCacheTask::CDiskCacheTask() : CRunnableTask(RTF_DEFAULT) { } CDiskCacheTask::~CDiskCacheTask() { ATOMICRELEASE(_pImageStore); } HRESULT CDiskCacheTask::PrepImage(HBITMAP * phBmp) { ASSERT(phBmp && *phBmp); DIBSECTION rgDIB; if (!GetObject(*phBmp, sizeof(rgDIB), &rgDIB)) { return E_FAIL; } // the disk cache only supports 32 Bpp DIBS now, so we can ignore the palette issue... ASSERT(rgDIB.dsBm.bmBitsPixel == 32); HBITMAP hBmpNew = NULL; HPALETTE hPal = NULL; if (SHGetCurColorRes() == 8) { hPal = SHCreateShellPalette(NULL); } IScaleAndSharpenImage2 * pScale; HRESULT hr = CoCreateInstance(CLSID_ThumbnailScaler, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IScaleAndSharpenImage2, &pScale)); if (SUCCEEDED(hr)) { hr = pScale->ScaleSharpen2((BITMAPINFO *) &rgDIB.dsBmih, rgDIB.dsBm.bmBits, &hBmpNew, &m_rgSize, SHGetCurColorRes(), hPal, 0, FALSE); pScale->Release(); } if (hPal) DeletePalette(hPal); if (SUCCEEDED(hr) && hBmpNew) { DeleteObject(*phBmp); *phBmp = hBmpNew; } return S_OK; }