// ========================================================================== // Class Implementation : COXFileWatcher // ========================================================================== // Source file : OXFileWatcher.cpp // Version: 9.3 // This software along with its related components, documentation and files ("The Libraries") // is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is // governed by a software license agreement ("Agreement"). Copies of the Agreement are // available at The Code Project (www.codeproject.com), as part of the package you downloaded // to obtain this file, or directly from our office. For a copy of the license governing // this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900. // ////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "OXFileWatcher.h" #include "OXMainRes.h" #ifdef _DEBUG #undef THIS_FILE static char BASED_CODE THIS_FILE[] = __FILE__; #endif #define new DEBUG_NEW #ifdef _DEBUG // Trace a message when the RESULT specifies failure #define CONDITIONAL_TRACE_RESULT(TEXT, RESULT) \ { if (FAILED(RESULT)) { \ TRACE(_T("%s : Failed (%u == 0x%X, Code : %u) :\n\t%s\n"), \ _T(TEXT), RESULT, RESULT, HRESULT_CODE(RESULT), COXFileWatcher::GetResultMessage(RESULT)); \ } } #else // Do not trace in Release build #define CONDITIONAL_TRACE_RESULT(TEXT, RESULT) #endif // _DEBUG ///////////////////////////////////////////////////////////////////////////// // COXFileWatcher // Data members ------------------------------------------------------------- // public: ///////////////////////////////////////////////////////////////////////////// // Definition of static members const DWORD COXFileWatcher::OXFileWatchChangeFileName = FILE_NOTIFY_CHANGE_FILE_NAME; // --- Any filename change in the watched directory or subtree causes a change notification const DWORD COXFileWatcher::OXFileWatchChangeDirName = FILE_NOTIFY_CHANGE_DIR_NAME; // --- Any directory-name change in the watched directory or subtree causes a change notification const DWORD COXFileWatcher::OXFileWatchChangeAttributes = FILE_NOTIFY_CHANGE_ATTRIBUTES; // --- Any attribute change in the watched directory or subtree causes a change notification const DWORD COXFileWatcher::OXFileWatchChangeSize = FILE_NOTIFY_CHANGE_SIZE; // --- Any file-size change in the watched directory or subtree causes a change notification // The operating system detects a change in file size only when the file is written to the disk. // For operating systems that use extensive caching, detection occurs only when the cache is // sufficiently flushed const DWORD COXFileWatcher::OXFileWatchChangeLastWrite = FILE_NOTIFY_CHANGE_LAST_WRITE; // --- Any change to the last write-time of files in the watched directory or subtree causes a // change notification. The operating system detects a change to the last write-time only when // the file is written to the disk. For operating systems that use extensive caching, detection // occurs only when the cache is sufficiently flushed const DWORD COXFileWatcher::OXFileWatchChangeLastAccess = FILE_NOTIFY_CHANGE_LAST_ACCESS; // --- Any change to the last access time of files in the watched directory or subtree causes // a change notification const DWORD COXFileWatcher::OXFileWatchChangeCreation = FILE_NOTIFY_CHANGE_CREATION; // --- Any change to the creation time of files in the watched directory or subtree causes // a change notification const DWORD COXFileWatcher::OXFileWatchChangeSecurity = FILE_NOTIFY_CHANGE_SECURITY; // --- Any security-descriptor change in the watched directory or subtree causes a change notification #if defined(_WIN32_WINNT) BOOL COXFileWatcher::g_bBackupPrivilegeGranted=FALSE; #endif // protected: // CArray m_arWatchedDirs; // --- Array with the watched directories // COXIDManager m_arFileWatchNotifiers; // --- Array with the waiting notifier objects // HRESULT m_hrError; // --- the HRESULT value of the last error // BOOL m_bThreadIsRunning; // --- TRUE if the thread which is used to watch file change notifications is running // CMutex m_mxThreadShouldStop; // --- Is used to stop the watching thread // CMutex m_mxThreadStopped; // --- Is used to inform the main thread that the watcher thread is stopped // and it is safe to make changes in m_arWatchedDirs // CWinThread* m_pWatcherThread; // --- A pointer to the watcher thread // HANDLE* m_pHandles; // --- Array with the watched handles // HANDLE* m_pNewHandles; // --- Array with the new handles after any changes in m_arWatchedDirs. // WORD m_nNumHandles; // --- the Number of watched handles // private: // Member functions --------------------------------------------------------- // public: COXFileWatcher::COXFileWatcher(BOOL bAddBackupPrivilege/*=TRUE*/) : m_hrError(ERROR_SUCCESS), m_bThreadIsRunning(FALSE), m_mxThreadShouldStop(TRUE), m_mxThreadStopped(FALSE), m_mxThreadCanContinue(FALSE), m_eventStartThread(FALSE,TRUE,NULL,NULL), m_pHandles(NULL), m_pNewHandles(NULL), m_pWatcherThread(NULL), m_nNumHandles(0), m_bDeletingThread(FALSE) { #if defined(_WIN32_WINNT) if(bAddBackupPrivilege) { if(!SetBackupPrivilege(TRUE)) { AfxThrowNotSupportedException(); } } #else UNREFERENCED_PARAMETER(bAddBackupPrivilege); #endif } COXFileWatcher::~COXFileWatcher() { // Will stop thread if it's running and will delete handles RemoveAllWatches(); // ... m_pWatcherThread may already be NULL delete m_pWatcherThread; } #if defined(_WIN32_WINNT) BOOL COXFileWatcher::SetBackupPrivilege(BOOL bAdd) { if(g_bBackupPrivilegeGranted==bAdd) { // already done return TRUE; } TOKEN_PRIVILEGES NewState; LUID luid; HANDLE hToken=NULL; BOOL bRtnStatus=TRUE; // Open the process token for this process. if(::OpenProcessToken( ::GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken)) { // Get the local unique id for the privilege. if(::LookupPrivilegeValue(NULL,_T("SeBackupPrivilege"),&luid)) { // Assign values to the TOKEN_PRIVILEGE structure. NewState.PrivilegeCount=1; NewState.Privileges[0].Luid=luid; NewState.Privileges[0].Attributes=(bAdd ? SE_PRIVILEGE_ENABLED : 0); // Adjust the token privilege if(!::AdjustTokenPrivileges( hToken,FALSE,&NewState,sizeof(NewState),NULL,NULL)) { bRtnStatus = FALSE; } } else { bRtnStatus = FALSE; } } else { bRtnStatus = FALSE; } if(hToken!=NULL) { ::CloseHandle(hToken); } if(bRtnStatus) { g_bBackupPrivilegeGranted=bAdd; } return bRtnStatus; } #endif BOOL COXFileWatcher::AddWatch(LPCTSTR pszPath, BOOL bWatchSubtree, DWORD dwWatchFilter) { #if defined (_WINDLL) #if defined (_AFXDLL) AFX_MANAGE_STATE(AfxGetAppModuleState()); #else AFX_MANAGE_STATE(AfxGetStaticModuleState()); #endif #endif ASSERT(pszPath != NULL); // Clear error flag m_hrError = ERROR_SUCCESS; COXWatchedDir* pwdNewEntry; // Make the class a Thread-Safe Class CSingleLock lockObj(&m_crArrayWatchedDirGuard); VERIFY(lockObj.Lock()); // Fisrt check if we are not watching for the same path for(int i=0; im_sPath == pszPath) { // ... OK. The path is already here COXWatchedDir* pwdPresentEntry = m_arWatchedDirs[i]; if(bWatchSubtree == pwdPresentEntry->m_bWatchSubTree && dwWatchFilter == pwdPresentEntry->m_dwWatchFilter) { // No modifications return TRUE; } pwdNewEntry = new COXWatchedDir( pszPath, // directory to be watched bWatchSubtree|pwdPresentEntry->m_bWatchSubTree, // flag for monitoring directory or directory tree dwWatchFilter|pwdPresentEntry->m_dwWatchFilter, // filter conditions to watch for IsExtendedInfoSupported()); // flag for extended info // Create new handle if(!pwdNewEntry->FindFirstHandle(m_hrError)) { goto error; } // Prepares the handles for the watcher thread m_pNewHandles = m_pHandles; VERIFY(m_mxThreadCanContinue.Lock()); // Informs the watcher thread that it can change the handles // and waits for the watcher thread to stop VERIFY(m_mxThreadShouldStop.Unlock()); // wait until the watcher thread stops VERIFY(m_mxThreadStopped.Lock()); // Update the entry for the given path m_arWatchedDirs.SetAt(i, pwdNewEntry); // Close old entry. This will close the old handle delete pwdPresentEntry; // Replace the old handle with the new one and start the // thread again m_pHandles[i+1]=pwdNewEntry->m_hEvent; VERIFY(m_mxThreadStopped.Unlock()); VERIFY(m_mxThreadShouldStop.Lock()); // notify watcher thread that it can go on executing VERIFY(m_mxThreadCanContinue.Unlock()); return TRUE; } } pwdNewEntry = new COXWatchedDir(pszPath, // directory to be watched bWatchSubtree, // flag for monitoring directory or directory tree dwWatchFilter, // filter conditions to watch for IsExtendedInfoSupported());// flag for extended info if(pwdNewEntry->FindFirstHandle(m_hrError)) { // Handles are m_nNumHandles, so we must allocate (m_nNumHandles+2) : one for the StartStopEvent // and one for the new entry m_pNewHandles= new HANDLE[m_nNumHandles + 2]; // Informs the watcher thread that it can change the handles if(m_bThreadIsRunning) { VERIFY(m_mxThreadCanContinue.Lock()); // notify watcher thread that it should stop VERIFY(m_mxThreadShouldStop.Unlock()); // wait until it stops DWORD dwResult=::WaitForSingleObject(m_mxThreadStopped.m_hObject, 0xFFFFFFFF); ASSERT(dwResult==NULL); //VERIFY(m_mxThreadStopped.Lock()); } // update the array of directories m_arWatchedDirs.Add(pwdNewEntry); if(0 < m_nNumHandles) { memcpy(m_pNewHandles,m_pHandles,(m_nNumHandles+1)*sizeof(HANDLE)); } else { m_pNewHandles[0]=HANDLE(m_mxThreadShouldStop); } m_pNewHandles[m_nNumHandles+1] = pwdNewEntry->m_hEvent; // ... m_pHandles may be NULL delete [] m_pHandles; m_pHandles = m_pNewHandles; m_nNumHandles++; // Starts the watcher thread if(m_bThreadIsRunning) { VERIFY(m_mxThreadStopped.Unlock()); VERIFY(m_mxThreadShouldStop.Lock()); // Unlocks the watcher thread VERIFY(m_mxThreadCanContinue.Unlock()); return TRUE; } else { // ... m_pWatcherThread may already be NULL delete m_pWatcherThread; VERIFY(m_eventStartThread.ResetEvent()); m_pWatcherThread=AfxBeginThread(FileWatchThreadFunction,this); if(m_pWatcherThread!=NULL) { m_pWatcherThread->m_bAutoDelete = FALSE; ::WaitForSingleObject(m_eventStartThread,INFINITE); return TRUE; } } } error: delete pwdNewEntry; CONDITIONAL_TRACE_RESULT("COXFileWatcher::AddWatch", m_hrError) return FALSE; } HRESULT COXFileWatcher::GetLastError() const { return m_hrError; } BOOL COXFileWatcher::IsExtendedInfoSupported() const { #if defined(_UNICODE) && (_WIN32_WINNT >= 0x400) // Extended info is supported OSVERSIONINFO OSVersion; OSVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); VERIFY(::GetVersionEx(&OSVersion)); // At the moment extended information is only supported on Windows NT 4.0 return (OSVersion.dwPlatformId==VER_PLATFORM_WIN32_NT && 4<=OSVersion.dwMajorVersion); #else // Extended info is not supported return FALSE; #endif //defined(_UNICODE) && (_WIN32_WINNT >= 0x400) } void COXFileWatcher::RemoveWatch(LPCTSTR pszPath) { #if defined (_WINDLL) #if defined (_AFXDLL) AFX_MANAGE_STATE(AfxGetAppModuleState()); #else AFX_MANAGE_STATE(AfxGetStaticModuleState()); #endif #endif ASSERT(pszPath != NULL); // Make the class a Thread-Safe Class CSingleLock lockObj(&m_crArrayWatchedDirGuard); VERIFY(lockObj.Lock()); // Fisrt find the given path in the local array for(int i=0; im_sPath==pszPath) { // If the handle is only one then stop the watcher thread if(m_nNumHandles == 1) m_pNewHandles = NULL; else m_pNewHandles=m_pHandles; VERIFY(m_mxThreadCanContinue.Lock()); // notify watcher thread that it should stop VERIFY(m_mxThreadShouldStop.Unlock()); // wait until it stops // VERIFY(m_mxThreadStopped.Lock()); m_mxThreadStopped.Lock(); // Move all handles after the current one forward for(int k=i+1; km_hThread, INFINITE); delete m_pWatcherThread; m_pWatcherThread=NULL; delete [] m_pHandles; m_pHandles=NULL; //end of the critical section, restore m_bDeletingThread m_bDeletingThread=FALSE; return; } } } void COXFileWatcher::RemoveAllWatches() { #if defined (_WINDLL) #if defined (_AFXDLL) AFX_MANAGE_STATE(AfxGetAppModuleState()); #else AFX_MANAGE_STATE(AfxGetStaticModuleState()); #endif #endif // Make the class a Thread-Safe Class CSingleLock lockObj(&m_crArrayWatchedDirGuard); VERIFY(lockObj.Lock()); StopThread(); if(m_arWatchedDirs.GetSize() == 0) return; for(int i=0; i<=m_arWatchedDirs.GetUpperBound(); i++) { delete m_arWatchedDirs[i]; } m_arWatchedDirs.RemoveAll(); if(m_pHandles!=NULL) { delete [] m_pHandles; m_pHandles = NULL; } m_nNumHandles = 0; } BOOL COXFileWatcher::OnNotify(COXFileWatchNotifier fileWatchNotifier) { return FALSE; } void COXFileWatcher::EnableWindowNotification(LPCTSTR pszPath, CWnd* pWnd, BOOL bPost) { ASSERT(pszPath!=NULL); ASSERT(pWnd!=NULL && pWnd->m_hWnd!=NULL); // Make the class a Thread-Safe Class CSingleLock lockObj(&m_crArrayWatchedDirGuard); VERIFY(lockObj.Lock()); // ... Looking for the path for(int i=0; i<=m_arWatchedDirs.GetUpperBound(); i++) { if((m_arWatchedDirs[i])->m_sPath == pszPath) { COXWatchedDir* pwdEntry = m_arWatchedDirs[i]; pwdEntry->m_hwndWindowToNotify = pWnd->m_hWnd; pwdEntry->m_bPost = bPost; break; } } } COXFileWatchNotifier COXFileWatcher::GetFileWatchNotifier(DWORD fileWatchNotifierID) { COXFileWatchNotifier* pfwnRequestedNotifier=m_arFileWatchNotifiers.GetItemPtr(fileWatchNotifierID); COXFileWatchNotifier aFileWatchNotifier; if(pfwnRequestedNotifier != NULL) { aFileWatchNotifier = *pfwnRequestedNotifier; // Remove the notifier object, so it is no more accessable m_arFileWatchNotifiers.RemoveItem(fileWatchNotifierID); delete pfwnRequestedNotifier; } return aFileWatchNotifier; } void COXFileWatcher::DisableWindowNotification(LPCTSTR pszPath) { ASSERT(pszPath!=NULL); // Make the class a Thread-Safe Class CSingleLock lockObj(&m_crArrayWatchedDirGuard); VERIFY(lockObj.Lock()); if(pszPath == NULL) return; // ... Looking for the path again for(int i=0; i<=m_arWatchedDirs.GetUpperBound(); i++) { if((m_arWatchedDirs[i])->m_sPath == pszPath) { COXWatchedDir* pwdEntry = m_arWatchedDirs[i]; pwdEntry->m_hwndWindowToNotify = NULL; break; } } } void COXFileWatcher::Empty() { RemoveAllWatches(); m_arFileWatchNotifiers.DeleteAll(); } #ifdef _DEBUG void COXFileWatcher::AssertValid() { // Make the class a Thread-Safe Class CSingleLock lockObj(&m_crArrayWatchedDirGuard); VERIFY(lockObj.Lock()); CObject::AssertValid(); for(int i=0; iAssertValid(); } } void COXFileWatcher::Dump(CDumpContext& dc) { // Make the class a Thread-Safe Class CSingleLock lockObj(&m_crArrayWatchedDirGuard); VERIFY(lockObj.Lock()); CObject::Dump(dc); dc << _T("\nNumber of watched paths: "); dc << m_arWatchedDirs.GetSize() << _T("\n"); if (dc.GetDepth() > 0) { for(int i=0; iDump(dc); } } } static TCHAR szUnknownError[] = _T("*** Unknown Error ***"); static DWORD dwLangID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); CString COXFileWatcher::GetResultMessage(HRESULT hResult) // --- In : hResult : The result code // --- Out : // --- Returns : A string containing a message of the specified code // --- Effect : Retrieves the error message corresponding to the given hResult { CString sResultMessage; LPTSTR pszMsgBuf = NULL; BOOL bUnknown = FALSE; DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM; // ... Remove the facility part if FACILITY_WIN32 if (HRESULT_FACILITY(hResult) == FACILITY_WIN32) hResult = HRESULT_CODE(hResult); // ... Get the actual message if (::FormatMessage(dwFlags, NULL, hResult, dwLangID, (LPTSTR)&pszMsgBuf, 0, NULL) == 0) { TRACE2("COXFileWatcher::GetResultMessage : No message was found for result code %i == 0x%8.8X\n", hResult, hResult); //pszMsgBuf = szUnknownError; VERIFY(sResultMessage.LoadString(IDS_OX_FILEWATCHERUNKERROR)); bUnknown = TRUE; } else sResultMessage = pszMsgBuf; // ... Clean up if (!bUnknown) LocalFree(pszMsgBuf); return sResultMessage; } #endif // _DEBUG // protected: UINT COXFileWatcher::FileWatchThreadFunction(LPVOID pParam) // --- In : pParam: a pointer to the current COXFileWatcher object // --- Out : // --- Returns : // --- Effect : Used as a worker thread function { #if defined (_WINDLL) #if defined (_AFXDLL) AFX_MANAGE_STATE(AfxGetAppModuleState()); #else AFX_MANAGE_STATE(AfxGetStaticModuleState()); #endif #endif ASSERT(pParam!=NULL); COXFileWatcher* pThis=(COXFileWatcher*)pParam; HANDLE* pHandles=pThis->m_pHandles; pThis->m_bThreadIsRunning = TRUE; VERIFY(pThis->m_eventStartThread.SetEvent()); #if defined(_UNICODE) && (_WIN32_WINNT >= 0x400) // Extended info is supported static TCHAR szFileName[_MAX_PATH]; // --- the name of the file if extended info is available static TCHAR szNewFileName[_MAX_PATH]; // --- the name of the new file if extended info is available and action is rename FILE_NOTIFY_INFORMATION* pfnInfo=NULL; // -- pointer to a buffer with extended information (if available) #endif // defined(_UNICODE) && (_WIN32_WINNT >= 0x400) COXFileWatchNotifier* pfwnNotifier; // --- the notifier object that will be posted/sended DWORD nCount=PtrToLong(pThis->m_arWatchedDirs.GetSize()+1); // Exit from the loop only when the StartStopEvent as signaled while(TRUE) { DWORD dwRetValue = WaitForMultipleObjects(nCount, pHandles, FALSE, INFINITE); // Check for error if(dwRetValue == WAIT_FAILED || dwRetValue (WAIT_OBJECT_0 + nCount - 1)) { pThis->m_hrError = HRESULT_FROM_WIN32(::GetLastError()); break; } else { // To stop the thread or a notification is signaled if(dwRetValue == WAIT_OBJECT_0) { VERIFY(pThis->m_mxThreadShouldStop.Unlock()); // Informs the main thread that we are waiting // VERIFY(pThis->m_mxThreadStopped.Unlock()); pThis->m_mxThreadStopped.Unlock(); // Wait for the main thread VERIFY(pThis->m_mxThreadCanContinue.Lock()); VERIFY(pThis->m_mxThreadStopped.Lock()); VERIFY(pThis->m_mxThreadCanContinue.Unlock()); // In this point the main thread should prepare the handles if(pThis->m_pNewHandles==NULL) { break; // Stops the thread } // Set new handles pHandles=pThis->m_pNewHandles; nCount = PtrToLong(pThis->m_arWatchedDirs.GetSize()+1); continue; } else { // ... OK. New notification //if notification comes in the period of the time //that this thread is to be deleted, so do not //process the notification. if (pThis->m_bDeletingThread) break; // Find the directory entry int nSignaledPath=dwRetValue-WAIT_OBJECT_0-1; COXWatchedDir* pwdPath = (pThis->m_arWatchedDirs)[nSignaledPath]; #if defined(_UNICODE) && (_WIN32_WINNT >= 0x400) // Extended info is supported DWORD dwNumberOfBytesTransferred; BOOL bValid; if(pwdPath->m_bExtended) { // ... OK. We have extended info. Take a pointer to it. pfnInfo=(FILE_NOTIFY_INFORMATION*)pwdPath->m_lpBuffer; // ... we have to check if the notification is valid or not // The check must be done before FindNextHandle() bValid= ::GetOverlappedResult(pwdPath->m_hDirectory, // handle of the directory pwdPath->m_pOverlapped, // address of overlapped structure &dwNumberOfBytesTransferred, // address of actual bytes count FALSE); /// wait flag if(bValid == FALSE || dwNumberOfBytesTransferred == 0) { // Something is wrong #ifdef _DEBUG if(bValid == FALSE) { pThis->m_hrError = HRESULT_FROM_WIN32(::GetLastError()); CONDITIONAL_TRACE_RESULT("COXFileWatcher::FileWatchThreadFunction", pThis->m_hrError) } else // dwNumberOfBytesTransferred == 0 { TRACE(_T("\nThe buffer passed to ReadDirectoryChangesW() is too small!\n")); } #endif // _DEBUG // ... update the handle if(!pwdPath->FindNextHandle((pThis->m_pHandles)[nSignaledPath+1])) { pThis->m_hrError= HRESULT_FROM_WIN32(::GetLastError()); break; } // The extended information is available, but it is not valid continue; } } while(TRUE) { if(pwdPath->m_bExtended) { // Extended information is available memcpy(szFileName,(LPCTSTR)&pfnInfo->FileName[0], pfnInfo->FileNameLength); szFileName[pfnInfo-> FileNameLength/sizeof(TCHAR)]=_T('\0'); if(pfnInfo->Action == FILE_ACTION_RENAMED_OLD_NAME) { pfnInfo=(FILE_NOTIFY_INFORMATION*)(((char*)pfnInfo)+ pfnInfo->NextEntryOffset); memcpy(szNewFileName, (LPCTSTR)&pfnInfo->FileName[0], pfnInfo->FileNameLength); szNewFileName[pfnInfo-> FileNameLength/sizeof(TCHAR)]=_T('\0'); } else { szNewFileName[0]=_T('\0'); } pfwnNotifier= new COXFileWatchNotifier(pwdPath->m_sPath, pwdPath->m_bWatchSubTree,pwdPath->m_dwWatchFilter, TRUE,pfnInfo->Action,szFileName,szNewFileName); } else #endif // defined(_UNICODE) && (_WIN32_WINNT >= 0x400) { // ... there is no extended information pfwnNotifier= new COXFileWatchNotifier(pwdPath->m_sPath, pwdPath->m_bWatchSubTree,pwdPath->m_dwWatchFilter, FALSE,0,NULL,NULL); } // Notify the derived class or ... if(!pThis->OnNotify(*pfwnNotifier) && pwdPath->m_hwndWindowToNotify!=NULL) { // ... post/send message UINT nItemIndex=pThis->m_arFileWatchNotifiers. AddItem(pfwnNotifier); if(pwdPath->m_bPost) { ::PostMessage(pwdPath->m_hwndWindowToNotify, WM_OX_FILE_NOTIFY,nItemIndex,LPARAM(0)); } else { ::SendMessage(pwdPath->m_hwndWindowToNotify, WM_OX_FILE_NOTIFY,nItemIndex,LPARAM(0)); } } else { // the notifier object is not queued so delete it delete pfwnNotifier; } #if defined(_UNICODE) && (_WIN32_WINNT >= 0x400) // Extended info is supported if(!pwdPath->m_bExtended || pfnInfo->NextEntryOffset == 0) { break; } else { // ... go to the next entry in the buffer... pfnInfo = (FILE_NOTIFY_INFORMATION*)(((char*)pfnInfo)+ pfnInfo->NextEntryOffset); } } // while(TRUE); #endif // defined(_UNICODE) && (_WIN32_WINNT >= 0x400) // ... update the handle if(!pwdPath->FindNextHandle((pThis->m_pHandles)[nSignaledPath+1])) { pThis->m_hrError = HRESULT_FROM_WIN32(::GetLastError()); break; } } } } // The thread is stopping pThis->m_bThreadIsRunning = FALSE; return 0; } void COXFileWatcher::StopThread() // --- In : // --- Out : // --- Returns : // --- Effect : Stops the thread { if(!m_bThreadIsRunning) return; VERIFY(m_mxThreadCanContinue.Lock()); // Informs the watcher thread that it can change the handles // and waits for the watcher thread to stop VERIFY(m_mxThreadShouldStop.Unlock()); // wait until the watcher thread stops // VERIFY(m_mxThreadStopped.Lock()); m_mxThreadStopped.Lock(); // The watcher thread will stop if there are no more handles to watch m_pNewHandles = NULL; VERIFY(m_mxThreadStopped.Unlock()); VERIFY(m_mxThreadShouldStop.Lock()); // notify watcher thread that it can go on executing VERIFY(m_mxThreadCanContinue.Unlock()); // Wait for the watcher thread to stops ::WaitForSingleObject(m_pWatcherThread->m_hThread, INFINITE); delete m_pWatcherThread; m_pWatcherThread = NULL; } // private: