//+------------------------------------------------------------------ // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1994 - 1998. // // File: tnupdr.cxx // //+------------------------------------------------------------------ #include "pch.cxx" #include #include "tnupdr.hxx" EXTERN_C const IID IID_IFlatStorage; /* b29d6138-b92f-11d1-83ee-00c04fc2c6d4 */ DECLARE_INFOLEVEL(updr) #define DEB_INFO DEB_USER1 #define DEB_REGINFO DEB_USER2 #define MIME_TYPES_ROOT L"software\\classes\\Mime\\Database\\Content Type" #define IMAGE_PREFIX L"image/" #define IMAGE_PREFIX_LEN 6 extern "C" CLSID CLSID_ThumbnailFCNHandler; extern "C" CLSID CLSID_ThumbnailUpdater; class CThumbnailCF : public IClassFactory { public: // Constructor CThumbnailCF(): _cRefs(1) { } // IUnknown methods STDMETHOD (QueryInterface) (REFIID riid, void **ppv); STDMETHOD_(ULONG,AddRef) (void); STDMETHOD_(ULONG,Release) (void); // IClassFactory methods STDMETHOD (CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppv); STDMETHOD (LockServer) (BOOL fLock) { return S_OK; } private: ULONG _cRefs; // CF reference count }; ////////////////////////////////////////////////////////////// // Funtion to create a Class Factory Object. // This is in form DoATClassCreate() wants. ////////////////////////////////////////////////////////////// HRESULT GetCThumbnailCF(REFCLSID clsid, REFIID iid, void **ppv) { HRESULT hr=S_OK; CThumbnailCF *pCF=NULL; *ppv = 0; if( ! IsEqualCLSID(clsid, CLSID_ThumbnailUpdater)) return CLASS_E_CLASSNOTAVAILABLE; pCF = new CThumbnailCF(); if( NULL == pCF ) return E_OUTOFMEMORY; hr = pCF->QueryInterface(iid, ppv); pCF->Release(); return hr; } ////////////////////////////////////////////////////////////// // Hook from the DllgetClassObject routine. // This is special because it must start in an Apartment. ////////////////////////////////////////////////////////////// HRESULT CThumbnail_ApartmentDllGetClassObject( REFCLSID clsid, REFIID iid, void **ppv) { HRESULT hr; updrDebug((DEB_ITRACE, "ApartmentDllGetClassObject(%I,%I,%x)\n", &clsid, &iid, ppv)); if(IsEqualCLSID(clsid, CLSID_ThumbnailUpdater)) { COleTls tls; if ( (tls->dwFlags & OLETLS_INNEUTRALAPT) || !(tls->dwFlags & OLETLS_APARTMENTTHREADED)) { //We need to switch to a single-threaded apartment. hr = DoATClassCreate(GetCThumbnailCF, clsid, iid, (IUnknown **)ppv); } else { //This thread is in a single-threaded apartment. hr = GetCThumbnailCF(clsid, iid, ppv); } } else { hr = CLASS_E_CLASSNOTAVAILABLE; } return hr; } //+------------------------------------------------------------------- // // Member: CThumbnailCF::AddRef, public // // Synopsis: Adds a reference to an interface // //-------------------------------------------------------------------- STDMETHODIMP_(ULONG) CThumbnailCF::AddRef(void) { return InterlockedIncrement((LONG *)&_cRefs); } //+------------------------------------------------------------------- // // Member: CThumbnailCF::Release, public // // Synopsis: Adds a reference to an interface // //-------------------------------------------------------------------- STDMETHODIMP_(ULONG) CThumbnailCF::Release(void) { ULONG cRefs = (ULONG) InterlockedDecrement((LONG *)&_cRefs); if (cRefs == 0) { delete this; } return cRefs; } //+------------------------------------------------------------------- // // Member: CThumbnailCF::QueryInterface, public // // Synopsis: Returns a pointer to the requested interface. // //-------------------------------------------------------------------- STDMETHODIMP CThumbnailCF::QueryInterface(REFIID riid, void **ppv) { if (IsEqualIID(riid, IID_IClassFactory) || IsEqualIID(riid, IID_IUnknown)) { *ppv = (IClassFactory *) this; AddRef(); return S_OK; } *ppv = NULL; return E_NOINTERFACE; } ////////////////////////////////////////////////////////////// // OLE32 Internal ClassFactory Entry Point ////////////////////////////////////////////////////////////// HRESULT CThumbnailCF_CreateInstance( IUnknown *pUnkOuter, REFIID riid, void** ppv) { HRESULT hr; CTNUpdater * pact; updrDebug(( DEB_IWARN, "Thumbnailer: CreateInstance called\n" )); if(NULL != pUnkOuter) { return CLASS_E_NOAGGREGATION; } pact = new CTNUpdater; if(NULL == pact) { return E_OUTOFMEMORY; } if(!pact->ObjectInit()) { pact->Release(); return E_OUTOFMEMORY; } hr = pact->QueryInterface(riid, ppv); pact->Release(); return hr; } //+------------------------------------------------------------------- // Member: CThumbnailCF::CreateInstance, public //-------------------------------------------------------------------- STDMETHODIMP CThumbnailCF::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) { return CThumbnailCF_CreateInstance( pUnkOuter, riid, ppv ); } ////////////////////////////////////////////////////////////// // CTNUpdater Class ////////////////////////////////////////////////////////////// //--------------------------------------------------------- // C++ Methods: Constructor //--------------------------------------------------------- CTNUpdater::CTNUpdater(void) { m_cRef = 1; m_pITE = NULL; m_hEvRegNotify = NULL; m_hkeyMime = NULL; m_ppwszExtensions = NULL; m_pTempList = NULL; } //--------------------------------------------------------- // C++ Methods: Destructor //--------------------------------------------------------- CTNUpdater::~CTNUpdater(void) { if( NULL != m_pTempList ) FreeTempExtensionList(); if( NULL != m_pITE ) m_pITE->Release(); if( NULL != m_hkeyMime ) CloseHandle( m_hkeyMime ); if( NULL != m_hEvRegNotify ) CloseHandle( m_hEvRegNotify ); if( NULL != m_ppwszExtensions ) CoTaskMemFree( m_ppwszExtensions ); } //--------------------------------------------------------- // non-interface Init routine //--------------------------------------------------------- BOOL CTNUpdater::ObjectInit() { HRESULT sc; DWORD status; // // Create the event for waiting on registry changes. // status = RegOpenKeyEx( HKEY_LOCAL_MACHINE, MIME_TYPES_ROOT, 0, KEY_ALL_ACCESS, &m_hkeyMime ); if( ERROR_SUCCESS != status ) updrErr( EH_Err, LAST_SCODE ); m_hEvRegNotify = CreateEvent( NULL, TRUE, // Manual Reset FALSE, // Init signaled NULL ); if( NULL == m_hEvRegNotify ) updrErr( EH_Err, LAST_SCODE ); updrChk( GetImageFileExtensions() ); return TRUE; EH_Err: return FALSE; } //--------------------------------------------------------- // IUnknown::AddRef //--------------------------------------------------------- ULONG CTNUpdater::AddRef() { return InterlockedIncrement((LONG*)&m_cRef); } //--------------------------------------------------------- // IUnknown::Release //--------------------------------------------------------- ULONG CTNUpdater::Release() { LONG cRef; cRef = InterlockedDecrement((LONG*)&m_cRef); // ASSERT(cRef >= 0); if(cRef <= 0) { delete this; } return cRef; } //--------------------------------------------------------- // IUnknown::QueryInterface //--------------------------------------------------------- HRESULT CTNUpdater::QueryInterface( REFIID iid, void **ppv) { HRESULT hr=S_OK; IUnknown *pUnk=NULL; if(IsEqualIID(IID_IUnknown, iid)) { pUnk = (IFilterStatus*)this; } else if(IsEqualIID(IID_IFilterStatus, iid)) { pUnk = (IFilterStatus*)this; } else if(IsEqualIID(IID_IOplockStorage, iid)) { pUnk = (IOplockStorage*)this; } else { return E_NOINTERFACE; } pUnk->AddRef(); *ppv = pUnk; return S_OK; } //--------------------------------------------------------- // IFilterStatus::Initialize //--------------------------------------------------------- HRESULT CTNUpdater::Initialize( const WCHAR *pwszCatalogName, const WCHAR *pwszCatalogPath) { return S_OK; } //--------------------------------------------------------- // IFilterStatus::FilterLoad //--------------------------------------------------------- HRESULT CTNUpdater::FilterLoad( const WCHAR *pwszPath, SCODE scFilterStatus) { return S_OK; } /* //--------------------------------------------- // Test routine //---------------------------------------- HRESULT test_junk(IStorage *pstg) { HRESULT sc=S_OK; IPropertySetStorage *ppss = NULL; IPropertyStorage *pps = NULL; PROPSPEC propSpec[1]; PROPVARIANT propVar[1]; updrChk( pstg->QueryInterface( IID_IPropertySetStorage, (void**)&ppss ) ); updrChk( ppss->Open( FMTID_SummaryInformation, STGM_READWRITE|STGM_SHARE_EXCLUSIVE, &pps ) ); propSpec[0].ulKind = PRSPEC_PROPID; propSpec[0].propid = PIDSI_THUMBNAIL; updrChk( pps->ReadMultiple(1, propSpec, propVar) ); EH_Err: // FreePropVariantArray(1, propVar); if( NULL != ppss ) ppss->Release(); if( NULL != pps ) pps->Release(); return sc; } */ //--------------------------------------------------------- // IFilterStatus::PreFilter // // This routine ALWAYS SUCCEEDS, unless we want to defer processing // to a later time. // If it returns any failure, CI will defer filtering the file // and will submit it again later. //--------------------------------------------------------- HRESULT CTNUpdater::PreFilter( WCHAR const * pwszPath) { HRESULT sc=S_OK; IStorage *pstg=NULL; IThumbnailExtractor *pITE=NULL; ITimeAndNoticeControl *pITNC=NULL; ULONG cStorageFinalReferences=0; updrDebug(( DEB_INFO | DEB_TRACE, "PreFilter(\"%ws\")\n", pwszPath )); // Optimize, don't spend time on files that we don't expect to be // Images. If it does not have a valid extension then we are done. // if( ! HasAnImageFileExtension(pwszPath) ) return S_FALSE; // Open an Oplock'ed Storage. (w/ SuppressChanges turned on) // updrChk( OpenStorageEx(pwszPath, STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, STGFMT_FILE, 0, IID_IFlatStorage, (void**)&pstg) ); if( NULL == m_pITE ) { updrChk( CoCreateInstance(CLSID_ThumbnailFCNHandler, NULL, // pUnkOuter CLSCTX_INPROC_SERVER, IID_IThumbnailExtractor, (void**)&pITE) ); m_pITE = pITE; pITE = NULL; } updrChk( m_pITE->OnFileUpdated(pstg) ); EH_Err: if(NULL != pITNC) pITNC->Release(); if(NULL != pITE) pITE->Release(); if(NULL != pstg) cStorageFinalReferences = pstg->Release(); updrAssert( 0 == cStorageFinalReferences ); updrDebug(( DEB_TRACE, "PreFilter() returned %x\n", sc )); switch( sc ) { case STG_E_SHAREVIOLATION: // Someone else has it open when we started. case STG_E_LOCKVIOLATION: case STG_E_REVERTED: // Someone else opened if after we started. break; // All other problems are likely permanent so don't error. // all errors tell CI to re-try the file. default: if( FAILED( sc ) ) sc = S_FALSE; else sc = S_OK; break; } return sc; } //--------------------------------------------------------- // IFilterStatus::PostFilter //--------------------------------------------------------- HRESULT CTNUpdater::PostFilter( WCHAR const * pwszPath, HRESULT hrStatus) { return S_OK; } //--------------------------------------------------------- // IOplockStorage::CreateStorageEx //--------------------------------------------------------- HRESULT CTNUpdater::CreateStorageEx( LPCWSTR pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, REFIID riid, void ** ppstgOpen) { return STG_E_INVALIDPARAMETER; } //--------------------------------------------------------- // IOplockStorage::OpenStorageEx //--------------------------------------------------------- HRESULT CTNUpdater::OpenStorageEx( LPCWSTR pwcsName, DWORD grfMode, DWORD stgfmt, DWORD grfAttrs, REFIID riid, void ** ppstgOpen) { HRESULT sc=S_OK; IStorage *pstg=NULL; if(0 != (grfMode & STGM_OPLOCKS_DONT_WORK)) return STG_E_INVALIDFLAG; if( STGFMT_NATIVE == stgfmt || STGFMT_DOCFILE == stgfmt || STGFMT_STORAGE == stgfmt) { updrErr(EH_Err, STG_E_INVALIDPARAMETER); } if( STGFMT_ANY == stgfmt ) updrErr(EH_Err, STG_E_INVALIDPARAMETER); if( STGFMT_FILE == stgfmt ) { updrChk( NFFOpen( pwcsName, // PathName grfMode, // Storage Mode // Special NFF flags NFFOPEN_OPLOCK | NFFOPEN_SUPPRESS_CHANGES, FALSE, // Create API? riid, // Interface ID (void**)&pstg ) ); // [out] interface pointer } *ppstgOpen = pstg; pstg = NULL; sc = S_OK; EH_Err: RELEASE_INTERFACE( pstg ); return(sc); } //--------------------------------------------------------- // HasAnImageFileExtention, private method. //--------------------------------------------------------- BOOL CTNUpdater::HasAnImageFileExtension( WCHAR const * pwszPath) { LPCWSTR pwszDot; LPWSTR* ppwszExt; HRESULT sc=S_OK; if(NULL == pwszPath) return FALSE; // // Scan forward through the string looking for the last dot that // was not followed by a WHACK or a SPACE. // for (pwszDot = NULL; *pwszPath; pwszPath++) { switch (*pwszPath) { case L'.': pwszDot = pwszPath; // remember the last dot break; case L'\\': case L' ': // extensions can't have spaces pwszDot = NULL; // forget last dot, it was in a directory break; } } // If there is no extension fail. // if(NULL == pwszDot) return FALSE; // If the registry change event is signaled then read a new list. // if( WAIT_OBJECT_0 == WaitForSingleObject( m_hEvRegNotify, 0 ) ) updrChk( GetImageFileExtensions() ); for( ppwszExt=m_ppwszExtensions; NULL!=*ppwszExt; ++ppwszExt) { // Do a case insensitive compare for a known extension. if( 0 == _wcsicmp( pwszDot, *ppwszExt ) ) return TRUE; } EH_Err: return FALSE; } HRESULT CTNUpdater::GetImageFileExtensions() { DWORD idx, ckeys; LPWSTR pwszKeyName = NULL; LPWSTR pwszExtension; ULONG ccName, ccMaxNameLength; DWORD status; HRESULT sc; FILETIME ftLastTime; int cszTotalStrs=0; // Count of Extension Strings int ccTotalChars=0; // Count of total Extension characters w/ NULLs int cbTotalSize=0; // Total memory to allocate. TEMPEXTLIST* pExt=NULL; // Src Pointer to the current Extension. LPWSTR* ppwszPtr=NULL; // Des Pointer into string pointer table. WCHAR* pwszBuf=NULL; // Des Pointer into character buffer table. LPWSTR* ppwszTable=NULL; if( NULL != m_ppwszExtensions ) { CoTaskMemFree( m_ppwszExtensions ); m_ppwszExtensions = NULL; } // // Ask for notification if this part of the registry changes. // Set the notify before reading, that way if a change is made while // we are reading and we miss it, we will pick it up on the next round. // status = RegNotifyChangeKeyValue( m_hkeyMime, TRUE, // watch subtree REG_NOTIFY_CHANGE_LAST_SET |REG_NOTIFY_CHANGE_NAME, m_hEvRegNotify, TRUE ); if( ERROR_SUCCESS != status ) { updrDebug(( DEB_ERROR, "Can't set RegNotifyChangeKeyValue() %x\n", GetLastError() )); } // // Get the size of the maximum entry. // Then allocate the buffer for the key name. // status = RegQueryInfoKey( m_hkeyMime, NULL, NULL, NULL, &ckeys, &ccMaxNameLength, NULL, NULL, NULL, NULL, NULL, NULL ); if(ERROR_SUCCESS != status) updrErr( EH_Err, LAST_SCODE ); ccMaxNameLength += 1; // Add one for the NULL updrMem( pwszKeyName = new WCHAR[ ccMaxNameLength * sizeof(WCHAR)] ); // // Enumerate through all the format types, looking for // the image types. (they start with "image/") // for(idx=0; idxpNext) { cszTotalStrs += 1; ccTotalChars += wcslen(pExt->pwszExtension) + 1; } // Allocate the memory // cbTotalSize = (cszTotalStrs+1) * sizeof (WCHAR*); cbTotalSize += ccTotalChars * sizeof(WCHAR*); ppwszTable = (LPWSTR*) CoTaskMemAlloc( cbTotalSize ); // Set the pointers to the start of the pointer table and // the start of the character buffer space. // ppwszPtr = ppwszTable; pwszBuf = (WCHAR*) (ppwszTable + cszTotalStrs+1); for(pExt=m_pTempList; pExt!=NULL; pExt=pExt->pNext) { // Copy the string into the buffer space. // wcscpy(pwszBuf, pExt->pwszExtension); // Record the enrty to point at it. // *ppwszPtr = pwszBuf; // Advance the pointers. // ppwszPtr += 1; // Advance one string pointer. pwszBuf += wcslen(pwszBuf) +1; // Advance one string. } // Place a NULL in the last entry, to terminate the list. // *ppwszPtr = NULL; FreeTempExtensionList(); // Success. Copy results out. // m_ppwszExtensions = ppwszTable; ppwszTable = NULL; EH_Err: if( NULL != ppwszTable ) CoTaskMemFree( ppwszTable ); if( NULL != pwszKeyName ) delete[] pwszKeyName; return S_OK; } // // Search the existing list for an identical extension. If you don't find // one, then push a new one onto the front of the list. // The string is given to us. If we don't want it, then delete it. // HRESULT CTNUpdater::AddToTempExtensionList( WCHAR* pwszExtension) { TEMPEXTLIST* pExt; for(pExt=m_pTempList; pExt!=NULL; pExt=pExt->pNext) { if( 0 == _wcsicmp(pwszExtension, pExt->pwszExtension) ) break; } if(NULL == pExt) { // If we didn't find it, save it on the list. pExt = new TEMPEXTLIST; pExt->pwszExtension = pwszExtension; pExt->pNext = m_pTempList; m_pTempList = pExt; } else { // If we already have one, then delete this string. CoTaskMemFree(pwszExtension); } return S_OK; } HRESULT CTNUpdater::FreeTempExtensionList() { TEMPEXTLIST* pExt; TEMPEXTLIST* pExtNext; pExt=m_pTempList; while(NULL != pExt) { pExtNext = pExt->pNext; CoTaskMemFree( pExt->pwszExtension ); delete pExt; pExt = pExtNext; } m_pTempList = NULL; return S_OK; } HRESULT CTNUpdater::GetAFileNameExtension( WCHAR* pwszKeyName, WCHAR** ppwszExtension) { DWORD status, dwType; HKEY hkeyImageFormat=NULL; DWORD cbExtBuf=0; HRESULT sc=S_OK; WCHAR *pwszExt=NULL; status = RegOpenKeyEx( m_hkeyMime, pwszKeyName, 0, KEY_ALL_ACCESS, &hkeyImageFormat ); if(ERROR_SUCCESS != status) { updrDebug(( DEB_ERROR, "Error, could not open image format key '%ws' err=%d\n", pwszKeyName, status )); updrErr( EH_Err, LAST_SCODE ); } // Get the Size of the extension string. // status = RegQueryValueEx( hkeyImageFormat, L"Extension", 0, &dwType, NULL, &cbExtBuf ); if(ERROR_SUCCESS != status) { if(ERROR_FILE_NOT_FOUND != status) { updrDebug(( DEB_REGINFO, "Error, reading Extension for '%ws' err=%d\n", pwszKeyName, status )); } sc = E_FAIL; goto EH_Err; } if(REG_SZ != dwType) { updrDebug(( DEB_REGINFO, "Error, Extension for '%ws' not REG_SZ type.\n", pwszKeyName )); sc = E_FAIL; goto EH_Err; } // Allocate the buffer and read the extension string. // updrMem( pwszExt = (WCHAR*)CoTaskMemAlloc(cbExtBuf) ); status = RegQueryValueEx( hkeyImageFormat, L"Extension", 0, &dwType, (BYTE*)pwszExt, &cbExtBuf ); if(ERROR_SUCCESS != status) updrErr( EH_Err, LAST_SCODE ); *ppwszExtension = pwszExt; pwszExt = NULL; EH_Err: if(NULL != pwszExt) CoTaskMemFree(pwszExt); if( NULL != hkeyImageFormat ) RegCloseKey(hkeyImageFormat); return sc; }