From eb34526ff7c034a120096db157278184ff946913 Mon Sep 17 00:00:00 2001 From: fcolin Date: Thu, 1 Feb 2007 13:07:29 +0000 Subject: Utilisateur : Fcolin Date : 8/11/02 Heure : 11:50 Créé Commentaire: (vss 1) --- IvyFileMon/DelayedDirectoryChangeHandler.cpp | 1414 ++++++++++++++++++++++++++ 1 file changed, 1414 insertions(+) create mode 100644 IvyFileMon/DelayedDirectoryChangeHandler.cpp (limited to 'IvyFileMon') diff --git a/IvyFileMon/DelayedDirectoryChangeHandler.cpp b/IvyFileMon/DelayedDirectoryChangeHandler.cpp new file mode 100644 index 0000000..1c4daf0 --- /dev/null +++ b/IvyFileMon/DelayedDirectoryChangeHandler.cpp @@ -0,0 +1,1414 @@ +// 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(); +} \ No newline at end of file -- cgit v1.1