// DelayedDirectoryChangeHandler.cpp: implementation of the CDelayedDirectoryChangeHandler2 class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "DirectoryChanges.h" #include "DelayedDirectoryChangeHandler.h" #include //for _beginthreadex #include // for PathMatchSpec #pragma comment( lib, "shlwapi.lib") // function #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #define UWM_DELAYED_DIRECTORY_NOTIFICATION (WM_APP+1024) HINSTANCE GetInstanceHandle() { return (HINSTANCE)GetModuleHandle(NULL); // ASSERT( AfxGetInstanceHandle() == (HINSTANCE)GetModuleHandle(NULL) ); <-- true for building .exe's //NOTE: In Dll's using shared MFC, AfxGetInstanceHandle() != (HINSTANCE)GetModuleHandle(NULL)... //don't know if this is the case for dll's using static MFC } static inline bool IsEmptyString(LPCTSTR sz) { return (bool)(sz==NULL || *sz == 0); } /********************************************************* PathMatchSpec() requires IE 4.0 or greater on NT... if running on NT 4.0 w/ out IE 4.0, then uses this function instead. Based on code by Jack Handy: http://www.codeproject.com/string/wildcmp.asp Changed slightly to match the PathMatchSpec signature, be unicode compliant & to ignore case by myself. *********************************************************/ #define _TESTING_WILDCMP_ONLY_ BOOL STDAPICALLTYPE wildcmp(LPCTSTR string, LPCTSTR wild ) { const TCHAR *cp, *mp; cp = mp = NULL; while ((*string) && (*wild != _T('*'))) { if ((_toupper(*wild) != _toupper(*string)) && (*wild != _T('?'))) { return FALSE; } wild++; string++; } while (*string) { if (*wild == _T('*')) { if (!*++wild) { return TRUE; } mp = wild; cp = string+1; } else if ((_toupper(*wild) == _toupper(*string)) || (*wild == _T('?'))) { wild++; string++; } else { wild = mp; string = cp++; } } while (*wild == _T('*')) { wild++; } return (!*wild)? TRUE : FALSE; } ////////////////////////////////////////////////////////////////////////// // //CDirChangeNotification member functions: // CDirChangeNotification::CDirChangeNotification(CDelayedDirectoryChangeHandler * pDelayedHandler, DWORD dwPartialPathOffset) :m_pDelayedHandler( pDelayedHandler ) ,m_szFileName1(NULL) ,m_szFileName2(NULL) ,m_dwError(0UL) ,m_dwPartialPathOffset(dwPartialPathOffset) { ASSERT( pDelayedHandler ); } CDirChangeNotification::~CDirChangeNotification() { if( m_szFileName1 ) free(m_szFileName1), m_szFileName1 = NULL; if( m_szFileName2 ) free(m_szFileName2), m_szFileName2 = NULL; } void CDirChangeNotification::DispatchNotificationFunction() { ASSERT( m_pDelayedHandler ); if( m_pDelayedHandler ) m_pDelayedHandler->DispatchNotificationFunction( this ); } void CDirChangeNotification::PostOn_FileAdded(LPCTSTR szFileName) { ASSERT( szFileName ); m_eFunctionToDispatch = eOn_FileAdded; m_szFileName1 = _tcsdup( szFileName) ; // // post the message so it'll be dispatch by another thread. PostNotification(); } void CDirChangeNotification::PostOn_FileRemoved(LPCTSTR szFileName) { ASSERT( szFileName ); m_eFunctionToDispatch = eOn_FileRemoved; m_szFileName1 = _tcsdup( szFileName) ; // // post the message so it'll be dispatched by another thread. PostNotification(); } void CDirChangeNotification::PostOn_FileNameChanged(LPCTSTR szOldName, LPCTSTR szNewName) { ASSERT( szOldName && szNewName ); m_eFunctionToDispatch = eOn_FileNameChanged; m_szFileName1 = _tcsdup( szOldName) ; m_szFileName2 = _tcsdup( szNewName) ; // // post the message so it'll be dispatched by another thread. PostNotification(); } void CDirChangeNotification::PostOn_FileModified(LPCTSTR szFileName) { ASSERT( szFileName ); m_eFunctionToDispatch = eOn_FileModified; m_szFileName1 = _tcsdup( szFileName ); // // post the message so it'll be dispatched by another thread. PostNotification(); } void CDirChangeNotification::PostOn_ReadDirectoryChangesError(DWORD dwError, LPCTSTR szDirectoryName) { ASSERT( szDirectoryName ); m_eFunctionToDispatch = eOn_ReadDirectoryChangesError; m_dwError = dwError; m_szFileName1 = _tcsdup(szDirectoryName); // // post the message so it'll be dispatched by the another thread. PostNotification(); } void CDirChangeNotification::PostOn_WatchStarted(DWORD dwError, LPCTSTR szDirectoryName) { ASSERT( szDirectoryName ); m_eFunctionToDispatch = eOn_WatchStarted; m_dwError = dwError; m_szFileName1 = _tcsdup(szDirectoryName); PostNotification(); } void CDirChangeNotification::PostOn_WatchStopped(LPCTSTR szDirectoryName) { ASSERT( szDirectoryName ); m_eFunctionToDispatch = eOn_WatchStopped; m_szFileName1 = _tcsdup(szDirectoryName); PostNotification(); } void CDirChangeNotification::PostNotification() { ASSERT( m_pDelayedHandler ); if( m_pDelayedHandler ) m_pDelayedHandler->PostNotification( this ); } static LRESULT CALLBACK DelayedNotificationWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) // // This is the wndproc for the notification window // // it's here to dispatch the notifications to the client // { if( message == UWM_DELAYED_DIRECTORY_NOTIFICATION ) { CDirChangeNotification * pNotification = reinterpret_cast(lParam); ASSERT( pNotification ); if( pNotification ) { DWORD dwEx(0); __try{ pNotification->DispatchNotificationFunction(); } __except(dwEx = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER){ //An exception was raised: // // Likely cause: there was a problem creating the CDelayedDirectoryChangeHandler::m_hWatchStoppedDispatchedEvent object // and the change handler object was deleted before the notification could be dispatched to this function. // // or perhaps, somebody's implementation of an overridden function caused an exception TRACE(_T("Following exception occurred: %d -- File: %s Line: %d\n"), dwEx, _T(__FILE__), __LINE__); } } return 0UL; } else return DefWindowProc(hWnd,message,wParam,lParam); } ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// // //CDelayedNotificationWindow static member vars: // long CDelayedNotificationWindow::s_nRefCnt = 0L; HWND CDelayedNotificationWindow::s_hWnd = NULL; BOOL CDelayedNotificationWindow::s_bRegisterWindow = FALSE; // // long CDelayedNotificationWindow::AddRef()//creates window for first time if necessary { if( InterlockedIncrement(&s_nRefCnt) == 1 || !::IsWindow( s_hWnd ) ) { TRACE(_T("CDelayedNotificationWindow -- Creating the notification window\n")); VERIFY( CreateNotificationWindow() ); } return s_nRefCnt; } long CDelayedNotificationWindow::Release()//destroys window for last time if necessary { long nRefCnt = -1; if( (nRefCnt = InterlockedDecrement(&s_nRefCnt)) == 0 ) { //no body else using the window so destroy it? TRACE(_T("CDelayedNotificationWindow -- Destroying the notification window\n")); DestroyWindow( s_hWnd ); s_hWnd = NULL; } return nRefCnt; } BOOL CDelayedNotificationWindow::RegisterWindowClass(LPCTSTR szClassName) // // registers our own window class to use as the hidden notification window. // { WNDCLASS wc = {0}; wc.style = 0; wc.hInstance = GetInstanceHandle(); wc.lpszClassName = szClassName; wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH ); wc.lpfnWndProc = DelayedNotificationWndProc; ATOM ant = RegisterClass( &wc ); if( ant == NULL ) { TRACE(_T("CDirChangeNotification::RegisterWindowClass - RegisterClass failed: %d\n"), GetLastError()); } return (BOOL)(ant!= NULL); } BOOL CDelayedNotificationWindow::CreateNotificationWindow() // // Create the hidden notification windows. // { TCHAR szClassName[] = _T("Delayed_Message_Sender"); if( !s_bRegisterWindow ) s_bRegisterWindow = RegisterWindowClass(szClassName); s_hWnd = CreateWindowEx(0, szClassName, _T("DelayedWnd"),0,0,0,0,0, NULL, 0, GetInstanceHandle(), NULL); if( s_hWnd == NULL ) { TRACE(_T("Unable to create notification window! GetLastError(): %d\n"), GetLastError()); TRACE(_T("File: %s Line: %d\n"), _T(__FILE__), __LINE__); } return (BOOL)(s_hWnd != NULL); } void CDelayedNotificationWindow::PostNotification(CDirChangeNotification * pNotification) // // Posts a message to a window created in the main // thread. // The main thread catches this message, and dispatches it in // the context of the main thread. // { ASSERT( pNotification ); ASSERT( s_hWnd ); ASSERT( ::IsWindow( s_hWnd ) ); PostMessage(s_hWnd, UWM_DELAYED_DIRECTORY_NOTIFICATION, 0, reinterpret_cast( pNotification )); // if you don't want the notification delayed, // // if( false ) // { // pNotification->DispatchNotificationFunction(); // } } ///////////////////////////////////////////////////////// // CDelayedNoticationThread // long CDelayedNotificationThread::s_nRefCnt = 0L; HANDLE CDelayedNotificationThread::s_hThread = NULL; DWORD CDelayedNotificationThread::s_dwThreadID = 0UL; void CDelayedNotificationThread::PostNotification(CDirChangeNotification * pNotification) { ASSERT( s_hThread != NULL ); ASSERT( s_dwThreadID != 0 ); if( !PostThreadMessage(s_dwThreadID, UWM_DELAYED_DIRECTORY_NOTIFICATION, 0, reinterpret_cast(pNotification)) ) { //Note, this can sometimes fail. //Will fail if: s_dwThreadID references a invalid thread id(the thread has died for example) // OR will fail if the thread doesn't have a message queue. // // This was failing because the thread had not been fully started by the time PostThreadMessage had been called // //Note: if this fails, it creates a memory leak because //the CDirChangeNotification object that was allocated and posted //to the thread is actually never going to be dispatched and then deleted.... it's //hanging in limbo..... // // The fix for this situation was to force the thread that starts // this worker thread to wait until the worker thread was fully started before // continueing. accomplished w/ an event... also.. posting a message to itself before signalling the // 'spawning' thread that it was started ensured that there was a message pump // associated w/ the worker thread by the time PostThreadMessage was called. TRACE(_T("PostThreadMessage() failed while posting to thread id: %d! GetLastError(): %d%s\n"), s_dwThreadID, GetLastError(), GetLastError() == ERROR_INVALID_THREAD_ID? _T("(ERROR_INVALID_THREAD_ID)") : _T("")); } } bool CDelayedNotificationThread::StartThread() { TRACE(_T("CDelayedNotificationThread::StartThread()\n")); ASSERT( s_hThread == NULL && s_dwThreadID == 0 ); s_hThread = (HANDLE)_beginthreadex(NULL,0, ThreadFunc, this, 0, (UINT*) &s_dwThreadID); if( s_hThread ) WaitForThreadStartup(); return s_hThread == NULL ? false : true; } bool CDelayedNotificationThread::StopThread() { TRACE(_T("CDelayedNotificationThread::StopThread()\n")); if( s_hThread != NULL && s_dwThreadID != 0 ) { PostThreadMessage(s_dwThreadID, WM_QUIT, 0,0); WaitForSingleObject(s_hThread, INFINITE); CloseHandle(s_hThread); s_hThread = NULL; s_dwThreadID = 0UL; return true; } return true;//already shutdown } UINT __stdcall CDelayedNotificationThread::ThreadFunc(LPVOID lpvThis) { //UNREFERENCED_PARAMETER( lpvThis ); // // Implements a simple message pump // CDelayedNotificationThread * pThis = reinterpret_cast(lpvThis); ASSERT( pThis ); // // Insure that this thread has a message queue by the time another // thread gets control and tries to use PostThreadMessage // problems can happen if someone tries to use PostThreadMessage // in between the time pThis->SignalThreadStartup() is called, // and the first call to GetMessage(); ::PostMessage(NULL, WM_NULL, 0,0);//if this thread didn't have a message queue before this, it does now. // // // Signal that this thread has started so that StartThread can continue. // if( pThis ) pThis->SignalThreadStartup(); TRACE(_T("CDelayedNotificationThread::ThreadFunc() ThreadID: %d -- Starting\n"), GetCurrentThreadId()); MSG msg; do{ while( GetMessage(&msg, NULL, 0,0) )//note GetMessage() can return -1, but only if i give it a bad HWND.(HWND for another thread for example)..i'm not giving an HWND, so no problemo here. { if( msg.message == UWM_DELAYED_DIRECTORY_NOTIFICATION ) { CDirChangeNotification * pNotification = reinterpret_cast( msg.lParam ); DWORD dwEx(0UL); __try{ if( pNotification ) pNotification->DispatchNotificationFunction(); } __except(dwEx = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER){ //An exception was raised: // // Likely causes: // * There was a problem creating the CDelayedDirectoryChangeHandler::m_hWatchStoppedDispatchedEvent object // and the change handler object was deleted before the notification could be dispatched to this function. // // * Somebody's implementation of an overridden virtual function caused an exception TRACE(_T("The following exception occurred: %d -- File: %s Line: %d\n"), dwEx, _T(__FILE__), __LINE__); } } else if( msg.message == WM_QUIT ) { break; } } }while( msg.message != WM_QUIT ); TRACE(_T("CDelayedNotificationThread::ThreadFunc() exiting. ThreadID: %d\n"), GetCurrentThreadId()); return 0; } long CDelayedNotificationThread::AddRef() { if( InterlockedIncrement(&s_nRefCnt) == 1 ) { VERIFY( StartThread() ); } return s_nRefCnt; } long CDelayedNotificationThread::Release() { if( InterlockedDecrement(&s_nRefCnt) <= 0 ) { s_nRefCnt = 0; VERIFY( StopThread() ); } return s_nRefCnt; } /////////////////////////////////////////////////////// //static member data for CDelayedDirectoryChangeHandler HINSTANCE CDelayedDirectoryChangeHandler::s_hShlwapi_dll = NULL;//for the PathMatchSpec() function BOOL CDelayedDirectoryChangeHandler::s_bShlwapi_dllExists = TRUE; long CDelayedDirectoryChangeHandler::s_nRefCnt_hShlwapi = 0L; FUNC_PatternMatchSpec CDelayedDirectoryChangeHandler::s_fpPatternMatchSpec = wildcmp;//default /////////////////////////////////////////////////////// //construction destruction CDelayedDirectoryChangeHandler::CDelayedDirectoryChangeHandler(CDirectoryChangeHandler * pRealHandler, bool bAppHasGUI, LPCTSTR szIncludeFilter, LPCTSTR szExcludeFilter, DWORD dwFilterFlags) : m_pDelayNotifier( NULL ) ,m_pRealHandler( pRealHandler ) ,m_szIncludeFilter(NULL) ,m_szExcludeFilter(NULL) ,m_dwFilterFlags( dwFilterFlags ) ,m_dwPartialPathOffset( 0UL ) ,m_hWatchStoppedDispatchedEvent(NULL) ,m_nNumIncludeFilterSpecs(0) ,m_nNumExcludeFilterSpecs(0) { ASSERT( m_pRealHandler ); InitializePathMatchFunc( szIncludeFilter, szExcludeFilter ); // // See that we're // m_hWatchStoppedDispatchedEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);//AUTO-RESET, not initially signalled ASSERT( m_hWatchStoppedDispatchedEvent ); if( bAppHasGUI ) { // // The value true was passed to the CDirectoryChangeWatcher constructor. // It's assumed that your app has a gui, that is, it implements // a message pump. To delay the notification to another thread, // we'll use a hidden notification window. // m_pDelayNotifier = new CDelayedNotificationWindow(); } else { // The value 'false' was passed to the CDirectoryChangeWatcher constructor. // // Your app has no message pump... use a class that implements one for you // in a worker thread. // // Notifications will be executed in this worker thread. // m_pDelayNotifier = new CDelayedNotificationThread(); } } CDelayedDirectoryChangeHandler::~CDelayedDirectoryChangeHandler() { if( m_pRealHandler ) delete m_pRealHandler, m_pRealHandler = NULL; if( m_pDelayNotifier ) delete m_pDelayNotifier, m_pDelayNotifier = NULL; if( m_hWatchStoppedDispatchedEvent ) CloseHandle(m_hWatchStoppedDispatchedEvent), m_hWatchStoppedDispatchedEvent = NULL; if( m_szIncludeFilter ){ if( m_nNumIncludeFilterSpecs == 1 ) free(m_szIncludeFilter); else { TCHAR ** ppTmp = (TCHAR**)m_szIncludeFilter; for(int i(0); i < m_nNumIncludeFilterSpecs; ++i) { free( *ppTmp++ ); } free( m_szIncludeFilter ); } m_szIncludeFilter = NULL; m_nNumIncludeFilterSpecs; } if( m_szExcludeFilter ) { if( m_nNumExcludeFilterSpecs == 1 ) free(m_szExcludeFilter); else{ TCHAR ** ppTmp = (TCHAR**)m_szExcludeFilter; for(int i(0); i < m_nNumExcludeFilterSpecs; ++i) { free( *ppTmp++ ); } free( m_szExcludeFilter ); } m_szExcludeFilter = NULL; m_nNumExcludeFilterSpecs = 0; } UninitializePathMatchFunc(); } BOOL CDelayedDirectoryChangeHandler::_PathMatchSpec(LPCTSTR szPath, LPCTSTR szPattern) { if( s_fpPatternMatchSpec ) { return s_fpPatternMatchSpec(szPath, szPattern); } ASSERT( FALSE ); return TRUE;//everything matches. } BOOL CDelayedDirectoryChangeHandler::InitializePathMatchFunc(LPCTSTR szIncludeFilter, LPCTSTR szExcludeFilter) // // // To support the Include and Exclude filters, the PathMatchSpec function is used. // PathMatchSpec is only supported on NT4.0 if IE 4.0 is installed. // // for the case where this code is running on NT 4.0 w/out IE 4.0, we use // a different function: wildcmp () // // // This function attempts to load shlwapi.dll dynamically and find the PathMatchSpec function. // // if the function PathMatchSpec can't be found, the function pointer s_fpPathMatchSpec is set to wildcmp. // // // Note: wildcmp doesn't support multiple file specs separated by a semi-colon // as PathMatchSpec does.... we'll support it by parsing them // when we want to test the filters, we'll call the pattern matching functions multiple times... // { // // Copy the include/exclude filters if specified... // // if( IsEmptyString(szIncludeFilter) && IsEmptyString(szExcludeFilter) ) { return TRUE;//both the include && exclude filters aren't specified //no need to initialize the pattern matching function.... //if filters are never used, then //one less dll is loaded. } #ifdef _TESTING_WILDCMP_ONLY_ s_hShlwapi_dll = NULL; s_bShlwapi_dllExists = FALSE; return InitializePatterns(szIncludeFilter, szExcludeFilter); #endif if( s_hShlwapi_dll != NULL ) { ASSERT( s_fpPatternMatchSpec != NULL ); InterlockedIncrement(&s_nRefCnt_hShlwapi); return InitializePatterns(szIncludeFilter, szExcludeFilter); } else{ if( s_bShlwapi_dllExists == TRUE )//either the dll exists, or we haven't tried loading it yet... { //The pattern match function hasn't been initialized yet.... // s_hShlwapi_dll = ::LoadLibrary(_T("Shlwapi.dll")); if( s_hShlwapi_dll == NULL ) { s_bShlwapi_dllExists = FALSE;//don't try loading this dll again. s_fpPatternMatchSpec = wildcmp;//even though it's set buy default, and this code will only get here once, set it just for fun. return InitializePatterns(szIncludeFilter, szExcludeFilter); } else { //Shlwapi.dll was found....check it for PathMatchSpec() #ifdef UNICODE s_fpPatternMatchSpec = (FUNC_PatternMatchSpec)::GetProcAddress(s_hShlwapi_dll, "PathMatchSpecW"); #else s_fpPatternMatchSpec = (FUNC_PatternMatchSpec)::GetProcAddress(s_hShlwapi_dll, "PathMatchSpecA"); #endif if( s_fpPatternMatchSpec != NULL ) { //UsesRealPathMatchSpec() will now return true. //we're on NT w/ IE 4.0 or greater...(or Win2k/XP) InterlockedIncrement(&s_nRefCnt_hShlwapi); return InitializePatterns(szIncludeFilter, szExcludeFilter); } else { //we found shlwapi.dll, but it didn't have PathMatchSpec() ::FreeLibrary( s_hShlwapi_dll ); s_hShlwapi_dll = NULL; s_bShlwapi_dllExists = FALSE; //instead of using PathMatchSpec() //we'll use wildcmp() s_fpPatternMatchSpec = wildcmp; //UsesRealPathMatchSpec() will now return false w/out asserting. return InitializePatterns(szIncludeFilter, szExcludeFilter); } } } } return (s_fpPatternMatchSpec != NULL); } BOOL CDelayedDirectoryChangeHandler::InitializePatterns(LPCTSTR szIncludeFilter, LPCTSTR szExcludeFilter) { ASSERT( !IsEmptyString(szIncludeFilter) //one of these must have something in it, || !IsEmptyString(szExcludeFilter) );//or else this function shouldn't be called. if( s_hShlwapi_dll != NULL ) { //we're using Shlwapi.dll's PathMatchSpec function.... //we're running on NT4.0 w/ IE 4.0 installed, or win2k/winXP(or greater) // // Copy the include/exclude filters if specified... // // // we're using the real PathMatchSpec() function which // supports multiple pattern specs...(separated by a semi-colon) // so there's only one filter spec as far as my code is concerned. // if( !IsEmptyString(szIncludeFilter) ) { m_szIncludeFilter = _tcsdup(szIncludeFilter); ASSERT( m_szIncludeFilter ); m_nNumIncludeFilterSpecs = 1; } if( !IsEmptyString(szExcludeFilter) ) { m_szExcludeFilter = _tcsdup(szExcludeFilter); ASSERT( m_szExcludeFilter ); m_nNumExcludeFilterSpecs = 1; } } else { //shlwapi.dll isn't on this machine.... can happen on NT4.0 w/ out IE 4.0 installed. ASSERT( s_bShlwapi_dllExists == FALSE ); // // we're using the function wildcmp() instead of PathMatchSpec.. // // this means that multiple pattern specs aren't supported... // in order to support them, we'll tokenize the string into multiple // pattern specs and test the string multiple times(once per pattern spec) // in order to support multiple patterns. // // // m_szIncludeFilter & m_szExclude filter will be used like TCHAR**'s instead of TCHAR*'s // m_nNumIncludeFilterSpecs = 0; if( !IsEmptyString(szIncludeFilter) ) { TCHAR * szTmpFilter = _tcsdup(szIncludeFilter); TCHAR * pTok = _tcstok( szTmpFilter, _T(";")); while( pTok ) { m_nNumIncludeFilterSpecs++; pTok = _tcstok(NULL, _T(";")); } if( m_nNumIncludeFilterSpecs == 1 ) m_szIncludeFilter = _tcsdup(szIncludeFilter); else { //allocate room for pointers .. one for each token... m_szIncludeFilter = (TCHAR*)malloc( m_nNumIncludeFilterSpecs * sizeof(TCHAR*)); free(szTmpFilter); szTmpFilter = _tcsdup(szIncludeFilter); pTok = _tcstok(szTmpFilter, _T(";")); TCHAR ** ppTmp = (TCHAR**)m_szIncludeFilter; while(pTok) { *ppTmp = _tcsdup(pTok); ppTmp++; pTok = _tcstok(NULL, _T(";")); } } free(szTmpFilter); } // // Do the same for the Exclude filter... // m_nNumExcludeFilterSpecs = 0; if( !IsEmptyString(szExcludeFilter) ) { TCHAR * szTmpFilter = _tcsdup(szExcludeFilter); TCHAR * pTok = _tcstok( szTmpFilter, _T(";")); while( pTok ) { m_nNumExcludeFilterSpecs++; pTok = _tcstok(NULL, _T(";")); } if( m_nNumExcludeFilterSpecs == 1 ) m_szExcludeFilter = _tcsdup(szExcludeFilter); else { //allocate room for pointers .. one for each token... m_szExcludeFilter = (TCHAR*)malloc( m_nNumExcludeFilterSpecs * sizeof(TCHAR*)); free(szTmpFilter); szTmpFilter = _tcsdup(szExcludeFilter); pTok = _tcstok(szTmpFilter, _T(";")); TCHAR ** ppTmp = (TCHAR**)m_szExcludeFilter; while(pTok) { *ppTmp = _tcsdup(pTok); ppTmp++; pTok = _tcstok(NULL, _T(";")); } } free(szTmpFilter); } } return (m_szExcludeFilter!= NULL || (m_szIncludeFilter != NULL)); } void CDelayedDirectoryChangeHandler::UninitializePathMatchFunc() { if( s_bShlwapi_dllExists == TRUE && s_hShlwapi_dll != NULL ) { if( InterlockedDecrement(&s_nRefCnt_hShlwapi) <= 0) { s_nRefCnt_hShlwapi = 0; FreeLibrary( s_hShlwapi_dll ); s_hShlwapi_dll = NULL; s_fpPatternMatchSpec = wildcmp; } } } bool CDelayedDirectoryChangeHandler::UsesRealPathMatchSpec() const //are we using PathMatchSpec() or wildcmp()? { if( s_hShlwapi_dll != NULL && s_fpPatternMatchSpec != NULL ) return true; if( s_hShlwapi_dll == NULL && s_fpPatternMatchSpec != NULL ) return false; ASSERT( FALSE );//this function was called before InitializePathMatchFunc() //oops! return false; } static inline bool HasTrailingBackslash(const CString & str ) { if( str.GetLength() > 0 && str[ str.GetLength() - 1 ] == _T('\\') ) return true; return false; } void CDelayedDirectoryChangeHandler::SetPartialPathOffset(const CString & strWatchedDirName) { if( m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_CHECK_PARTIAL_PATH ) { //set the offset to if( HasTrailingBackslash( strWatchedDirName ) ) m_dwPartialPathOffset = strWatchedDirName.GetLength(); else m_dwPartialPathOffset = strWatchedDirName.GetLength() + 1; } else m_dwPartialPathOffset = 0; } CDirChangeNotification * CDelayedDirectoryChangeHandler::GetNotificationObject() // // Maybe in future I'll keep a pool of these // objects around to increase performance... // using objects from a cache will be faster // than allocated and destroying a new one each time. // // { ASSERT( m_pRealHandler ); return new CDirChangeNotification(this, m_dwPartialPathOffset);//helps support FILTERS_CHECK_PARTIAL_PATH } void CDelayedDirectoryChangeHandler::DisposeOfNotification(CDirChangeNotification * pNotification) { delete pNotification; } //These functions are called when the directory to watch has had a change made to it void CDelayedDirectoryChangeHandler::On_FileAdded(const CString & strFileName) { CDirChangeNotification * p = GetNotificationObject(); ASSERT( p ); if( p ) p->PostOn_FileAdded( strFileName ); } void CDelayedDirectoryChangeHandler::On_FileRemoved(const CString & strFileName) { CDirChangeNotification * p = GetNotificationObject(); ASSERT( p ); if( p ) p->PostOn_FileRemoved( strFileName ); } void CDelayedDirectoryChangeHandler::On_FileModified(const CString & strFileName) { CDirChangeNotification * p = GetNotificationObject(); ASSERT( p ); if( p ) p->PostOn_FileModified( strFileName ); } void CDelayedDirectoryChangeHandler::On_FileNameChanged(const CString & strOldFileName, const CString & strNewFileName) { CDirChangeNotification * p = GetNotificationObject(); ASSERT( p ); if( p ) p->PostOn_FileNameChanged( strOldFileName, strNewFileName ); } void CDelayedDirectoryChangeHandler::On_ReadDirectoryChangesError(DWORD dwError, const CString & strDirectoryName) { CDirChangeNotification * p = GetNotificationObject(); ASSERT( p ); if( p ) p->PostOn_ReadDirectoryChangesError( dwError, strDirectoryName ); } void CDelayedDirectoryChangeHandler::On_WatchStarted(DWORD dwError, const CString & strDirectoryName) { if( !(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_NO_WATCHSTART_NOTIFICATION)) { CDirChangeNotification * p = GetNotificationObject(); if( p ) p->PostOn_WatchStarted(dwError, strDirectoryName); } } void CDelayedDirectoryChangeHandler::On_WatchStopped(const CString & strDirectoryName) { if( !(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_NO_WATCHSTOP_NOTIFICATION)) { CDirChangeNotification * p = GetNotificationObject(); if( p ){ if( m_hWatchStoppedDispatchedEvent ) ::ResetEvent(m_hWatchStoppedDispatchedEvent); p->PostOn_WatchStopped( strDirectoryName ); // Wait that this function has been dispatched to the other thread // before continueing. This object may be getting deleted // soon after this function returns, and before the function can be // dispatched to the other thread.... WaitForOnWatchStoppedDispatched(); } } } void CDelayedDirectoryChangeHandler::PostNotification(CDirChangeNotification * pNotification) { if( m_pDelayNotifier ) m_pDelayNotifier->PostNotification( pNotification ); } inline bool IsNonFilterableEvent( CDirChangeNotification::eFunctionToDispatch eEvent) // Helper function // For filtering events..... these functions can not be filtered out. // { if( eEvent == CDirChangeNotification::eOn_WatchStarted || eEvent == CDirChangeNotification::eOn_WatchStopped || eEvent == CDirChangeNotification::eOn_ReadDirectoryChangesError ) { return true; } else return false; } DWORD GetPathOffsetBasedOnFilterFlags(CDirChangeNotification * pNot, DWORD dwFilterFlags) { ASSERT( pNot && dwFilterFlags != 0 ); //helps support the filter options FILTERS_CHECK_FULL_PATH, FILTERS_CHECK_PARTIAL_PATH, and FILTERS_CHECK_FILE_NAME_ONLY DWORD dwFileNameOffset = 0;//offset needed to support FILTERS_CHECK_FULL_PATH if( dwFilterFlags & CDirectoryChangeWatcher::FILTERS_CHECK_FILE_NAME_ONLY ) { //set the offset to support FILTERS_CHECK_FILE_NAME_ONLY TCHAR * pSlash = _tcsrchr(pNot->m_szFileName1, _T('\\')); if( pSlash ) dwFileNameOffset = (++pSlash - pNot->m_szFileName1); // // Because file name change notifications take place in the same directory, // the same dwFileNameOffset can be used for the szNewFileName(pNot->m_szFileName2) // when checking the filter against the new file name. // } else if( dwFilterFlags & CDirectoryChangeWatcher::FILTERS_CHECK_PARTIAL_PATH) { // // partial path offset is the offset // from the beginning of the file name, // to the end of the watched directory path... // ie: If you're watching "C:\Temp" // and the file C:\Temp\SubFolder\FileName.txt" is changed, // the partial path offset will give you "SubFolder\FileName.txt" // when this is checked against the include/exclude filter. // dwFileNameOffset = pNot->m_dwPartialPathOffset; } //else //if( m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_CHECK_FULL_PATH ) // dwFileNameOffset = 0; return dwFileNameOffset; } bool CDelayedDirectoryChangeHandler::NotifyClientOfFileChange(CDirChangeNotification * pNot) // // // Perform the tests to see if the client wants to be notified of this // file change notification. // // Tests performed: // // Event test: Not all events can be filtered out. // On_ReadDirectoryChangesError // cannot be filtered out. // Filter flags test: User can specify flags so that no tests are performed....all notifications are sent to the user. // // Filter test: Test the notification file name against include and exclude filters. // // Only files changes matching the INCLUDE filter will be passed to the client. // By not specifying an include filter, all file changes are passed to the client. // // Any files matching the EXCLUDE filter will not be passed to the client. // // // Note: For the file name change event: // If the old file name does not pass the tests for the include and exclude filter // but the NEW file name does pass the test for the filters, then the client IS notified. // // Client test: The CDirectoryChangeHandler derived class is given a chance to filter the event by calling // CDirectoryChangeHandler::On_FilterNotification() // // RETURN VALUE: // If this function returns true, the notification function is called. // If it returns false, the notification is ignored. // The client is notified by calling CDirectoryChangeHandler's virtual functions On_FileAdded(),On_FileRemoved(), etc. { ASSERT( pNot ); ASSERT( m_pRealHandler ); // // Some events can't be ignored, or filtered out. // if( ((m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_DONT_USE_ANY_FILTER_TESTS) == CDirectoryChangeWatcher::FILTERS_DONT_USE_ANY_FILTER_TESTS) || IsNonFilterableEvent( pNot->m_eFunctionToDispatch ) ) { // Either this is a non-filterable event, or we're not using any filters... // client is notified of all events.. return true; } // // See if user wants to test CDirectoryChangeHandler::On_FilterNotification() // before tests are performed against the file name, and filter specifications // if( (m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_TEST_HANDLER_FIRST )//specified that CDirectoryChangeHandler::On_FilterNotification is to be called before any other filter tests && !(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_DONT_USE_HANDLER_FILTER)//and did not specify that this CDirectoryChangeHandler::On_FilterNotification is not to be called.. && !m_pRealHandler->On_FilterNotification(pNot->m_eFunctionToDispatch, pNot->m_szFileName1, pNot->m_szFileName2) ) { // // Client specified to test handler first, and it didn't pass the test... don't notify the client. // return false; } //else // // this file change passed the user test, continue testing // to see if it passes the filter tests. DWORD dwFileNameOffset = GetPathOffsetBasedOnFilterFlags(pNot, m_dwFilterFlags); // // See if the changed file matches the include or exclude filter // Only allow notifications for included files // that have not been exluded. // if(!(m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_DONT_USE_FILTERS) ) { if( false == IncludeThisNotification(pNot->m_szFileName1 + dwFileNameOffset) || true == ExcludeThisNotification(pNot->m_szFileName1 + dwFileNameOffset) ) { if( pNot->m_eFunctionToDispatch != CDirChangeNotification::eOn_FileNameChanged ) return false; else{ //Special case for file name change: // // the old file name didn't pass the include/exclude filter // but if the new name passes the include/exclude filter, // we will pass it on to the client... if( false == IncludeThisNotification(pNot->m_szFileName2 + dwFileNameOffset) || true == ExcludeThisNotification(pNot->m_szFileName2 + dwFileNameOffset) ) { // the new file name didn't pass the include/exclude filter test either // so don't pass the notification on... return false; } } } } // // Finally, let the client determine whether or not it wants this file notification // if this test has not already been performed... // if( (m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_TEST_HANDLER_FIRST) || (m_dwFilterFlags & CDirectoryChangeWatcher::FILTERS_DONT_USE_HANDLER_FILTER) ) { // if we got this far, and this flag was specified, // it's already passed this test, // or we're not checking it based on the filter flag FILTERS_DONT_USE_HANDLER_FILTER.... return true; } else { if( m_pRealHandler->On_FilterNotification(pNot->m_eFunctionToDispatch, pNot->m_szFileName1, pNot->m_szFileName2) ) { return true; } else { //else client's derived CDirectoryChangeHandler class chose // not to be notified of this file change return false; } } } bool CDelayedDirectoryChangeHandler::IncludeThisNotification(LPCTSTR szFileName) // // The Include filter specifies which file names we should allow to notifications // for... otherwise these notifications are not dispatched to the client's code. // // Tests the file name to see if it matches a filter specification // // RETURN VALUES: // // true : notifications for this file are to be included... // notifiy the client by calling the appropriate CDirectoryChangeHandler::On_Filexxx() function. // false: this file is not included.... do not notifiy the client... // { ASSERT( szFileName ); if( m_szIncludeFilter == NULL ) // no filter specified, all files pass.... return true; if( m_nNumIncludeFilterSpecs == 1 ) { return _PathMatchSpec(szFileName, m_szIncludeFilter)? true : false; } else { TCHAR ** ppTmp = (TCHAR**)m_szIncludeFilter; for(int i(0); i < m_nNumIncludeFilterSpecs; ++i ) { if( _PathMatchSpec(szFileName, *ppTmp++) ) return true; } return false; } return false; } bool CDelayedDirectoryChangeHandler::ExcludeThisNotification(LPCTSTR szFileName) // // Tests the file name to see if it matches a filter specification // if this function returns true, it means that this notification // is NOT to be passed to the client.... changes to this kind of file // are not // // RETURN VALUES: // // true : notifications for this file are to be filtered out(EXCLUDED)... // do not notifify the client code. // false: notifications for this file are NOT to be filtered out // // { ASSERT( szFileName ); if( m_szExcludeFilter == NULL ) // no exclude filter... nothing is excluded... return false; if( m_nNumExcludeFilterSpecs == 1 ) { if( _PathMatchSpec(szFileName, m_szExcludeFilter) ) return true;//exclude this notification... return false; } else { TCHAR ** ppTmp = (TCHAR**)m_szExcludeFilter; for(int i(0); i < m_nNumExcludeFilterSpecs; ++i ) { if( _PathMatchSpec(szFileName, *ppTmp++) ) return true;//exclude this one... } return false;//didn't match any exclude filters...don't exclude it } /** if( m_szExcludeFilter == NULL //no exclude filter specified, not excluding anything.... || !PathMatchSpec(szFileName, m_szExcludeFilter) )//or didn't match filter pattern.. this is not excluded... { return false; } return true; ***/ } void CDelayedDirectoryChangeHandler::DispatchNotificationFunction(CDirChangeNotification * pNotification) /***************************************************** This function is called when we want the notification to execute. ******************************************************/ { ASSERT( m_pRealHandler ); ASSERT( pNotification ); if( pNotification && m_pRealHandler ) { // // Allow the client to ignore the notification // // if( NotifyClientOfFileChange(pNotification)) { switch( pNotification->m_eFunctionToDispatch ) { case CDirChangeNotification::eOn_FileAdded: m_pRealHandler->On_FileAdded( pNotification->m_szFileName1 ); break; case CDirChangeNotification::eOn_FileRemoved: m_pRealHandler->On_FileRemoved( pNotification->m_szFileName1 ); break; case CDirChangeNotification::eOn_FileNameChanged: m_pRealHandler->On_FileNameChanged( pNotification->m_szFileName1, pNotification->m_szFileName2 ); break; case CDirChangeNotification::eOn_FileModified: m_pRealHandler->On_FileModified( pNotification->m_szFileName1 ); break; case CDirChangeNotification::eOn_ReadDirectoryChangesError: m_pRealHandler->On_ReadDirectoryChangesError( pNotification->m_dwError, pNotification->m_szFileName1 ); break; case CDirChangeNotification::eOn_WatchStarted: m_pRealHandler->On_WatchStarted(pNotification->m_dwError, pNotification->m_szFileName1); break; case CDirChangeNotification::eOn_WatchStopped: try{ // // The exception handler is just in case of the condition described in DirectoryChanges.h // in the comments for On_WatchStopped() // m_pRealHandler->On_WatchStopped(pNotification->m_szFileName1); }catch(...){ MessageBeep( 0xffff ); MessageBeep( 0xffff ); #ifdef DEBUG MessageBox(NULL,_T("An RTFM Exception was raised in On_WatchStopped() -- see Comments for CDirectoryChangeHandler::On_WatchStopped() in DirectoryChanges.h."), _T("Programmer Note(DEBUG INFO):"), MB_ICONEXCLAMATION | MB_OK); #endif } // // Signal that the On_WatchStopped() function has been dispatched. // if( m_hWatchStoppedDispatchedEvent ) SetEvent(m_hWatchStoppedDispatchedEvent); break; case CDirChangeNotification::eFunctionNotDefined: default: break; }//end switch() } } if( pNotification ) // DisposeOfNotification(pNotification);// deletes or releases the notification object from memory/use // } BOOL CDelayedDirectoryChangeHandler::WaitForOnWatchStoppedDispatched( ) // // When shutting down, m_pRealHandler->On_WatchStopped() will be called. // Because it's possible that this object will be deleted before the notification // can be dispatched to the other thread, we have to wait until we know that it's been executed // before returning control. // // This function signals that the function has been dispatched to the other // thread and it will be safe to delete this object once this has returned. // { ASSERT( m_hWatchStoppedDispatchedEvent ); DWORD dwWait = WAIT_FAILED; if( m_hWatchStoppedDispatchedEvent ) { if( m_bAppHasGUI == false ) { // // The function will be dispatched to another thread... // just wait for the event to be signalled.... do{ dwWait = WaitForSingleObject(m_hWatchStoppedDispatchedEvent, 5000);//wait five seconds if( dwWait != WAIT_OBJECT_0 ) { TRACE(_T("WARNING: Possible Deadlock detected! ThreadID: %d File: %s Line: %d\n"), GetCurrentThreadId(), _T(__FILE__), __LINE__); } }while( dwWait != WAIT_OBJECT_0 ); } else { // // Note to self: This thread doesn't have a message Q, and therefore can't attach to // receive messages and process them... MsgWaitForMultipleObjects won't wake up for messages // unless i attach myself the the other threads input Q.... // just use MsgWaitForMultipleObjects() in place of WaitForSingleObject in the places where it's used... // do{ dwWait = MsgWaitForMultipleObjects(1, &m_hWatchStoppedDispatchedEvent, FALSE, 5000, QS_ALLEVENTS);//wake up for all events, sent messages, posted messages etc. switch(dwWait) { case WAIT_OBJECT_0: { // // The event has become signalled // }break; case WAIT_OBJECT_0 + 1: { // // There is a message in this thread's queue, so // MsgWaitForMultipleObjects returned. // Process those messages, and wait again. MSG msg; while( PeekMessage(&msg, NULL, 0,0, PM_REMOVE ) ) { if( msg.message != WM_QUIT) { TranslateMessage(&msg); DispatchMessage(&msg); } else { /**** NOTE: putting WM_QUIT back in the Q caused problems. forget about it. ****/ break; } } }break; case WAIT_TIMEOUT: { TRACE(_T("WARNING: Possible Deadlock detected! ThreadID: %d File: %s Line: %d\n"), GetCurrentThreadId(), _T(__FILE__), __LINE__); }break; } }while( dwWait != WAIT_OBJECT_0 ); ASSERT( dwWait == WAIT_OBJECT_0 ); } } else { TRACE(_T("WARNING: Unable to wait for notification that the On_WatchStopped function has been dispatched to another thread.\n")); TRACE(_T("An Exception may occur shortly.\n")); TRACE(_T("File: %s Line: %d"), _T( __FILE__ ), __LINE__); } return (dwWait == WAIT_OBJECT_0 ); } void CDelayedDirectoryChangeHandler::SetChangedDirectoryName(const CString & strChangedDirName) { ASSERT( m_pRealHandler ); CDirectoryChangeHandler::SetChangedDirectoryName(strChangedDirName); if( m_pRealHandler ) m_pRealHandler->SetChangedDirectoryName( strChangedDirName ); } const CString & CDelayedDirectoryChangeHandler::GetChangedDirectoryName() const { if( m_pRealHandler ) return m_pRealHandler->GetChangedDirectoryName(); return CDirectoryChangeHandler::GetChangedDirectoryName(); }