474 lines
14 KiB
C++
474 lines
14 KiB
C++
|
#include "priv.h"
|
||
|
#include "sccls.h"
|
||
|
#include "runtask.h"
|
||
|
#include "legacy.h"
|
||
|
|
||
|
#include <ntquery.h> // 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;
|
||
|
}
|