926 lines
25 KiB
C++
926 lines
25 KiB
C++
// ==========================================================================
|
|
// 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<COXWatchedDir*, COXWatchedDir*> m_arWatchedDirs;
|
|
// --- Array with the watched directories
|
|
|
|
// COXIDManager<COXFileWatchNotifier> 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; i<m_arWatchedDirs.GetSize(); i++)
|
|
{
|
|
if((m_arWatchedDirs[i])->m_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; i<m_arWatchedDirs.GetSize(); i++)
|
|
{
|
|
if((m_arWatchedDirs[i])->m_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; k<m_nNumHandles; k++)
|
|
{
|
|
m_pHandles[k]=m_pHandles[k+1];
|
|
}
|
|
m_nNumHandles--;
|
|
|
|
COXWatchedDir* pwdEntry = m_arWatchedDirs[i];
|
|
m_arWatchedDirs.RemoveAt(i);
|
|
delete (COXWatchedDir*)pwdEntry;
|
|
|
|
//critical section, before let the thread to go on
|
|
//set m_bDeletingThread to TRUE to inform the thread
|
|
//that we are deleting it. Otherwise, may came notification
|
|
//from windows exactly in the period time we have deleted
|
|
//the list watches, but thread had no time to finished,
|
|
//so, the thread receives notification while
|
|
//::WaitForSingleObject() in this function
|
|
if (m_pNewHandles==NULL)
|
|
m_bDeletingThread=TRUE;
|
|
|
|
VERIFY(m_mxThreadStopped.Unlock());
|
|
VERIFY(m_mxThreadShouldStop.Lock());
|
|
// notify watcher thread that it can go on executing
|
|
VERIFY(m_mxThreadCanContinue.Unlock());
|
|
|
|
if(m_pNewHandles!=NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Wait for the watcher thread to stops
|
|
::WaitForSingleObject(m_pWatcherThread->m_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; i<m_arWatchedDirs.GetSize(); i++)
|
|
{
|
|
COXWatchedDir* pwd = m_arWatchedDirs[i];
|
|
pwd->AssertValid();
|
|
}
|
|
}
|
|
|
|
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; i<m_arWatchedDirs.GetSize(); i++)
|
|
{
|
|
COXWatchedDir* pwd = m_arWatchedDirs[i];
|
|
pwd->Dump(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 ||
|
|
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:
|
|
|