////////////////////////////////////////////////////////////////
// CNGFileChangeMonitor Copyright 1999 by Anna-Jayne Metcalfe (code@annasplace.me.uk)
//
//
// NGFileChangeMonitor.cpp : implementation file
//
#include "StdAfx.h"
#include <sys/types.h>
#include <sys/stat.h>
#include "NGFileChangeMonitor.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CNGFileChangeMonitor class
// Static map object used to map from thread data to object instances
// Needed as the thread function is a static
static CMapPtrToPtr g_ThreadParamMap;
/////////////////////////////////////////////////////////////////////////////
// CNGFileChangeMonitor construction/destruction
CNGFileChangeMonitor::CNGFileChangeMonitor(void)
{
m_pThread = NULL;
m_hThread = NULL;
}
CNGFileChangeMonitor::~CNGFileChangeMonitor(void)
{
DestroyMonitoringThread();
}
/////////////////////////////////////////////////////////////////////////
// Helper methods to create/destroy thread for detecting file system changes
// Structure used to pass data into the worker thread
// This is needed for the implementation code below ONLY
typedef struct THREADINFO_TAG
{
HANDLE hEvent; // Event handle for terminate notifications
HWND hWnd; // Target window
UINT nMsg; // User defined message to post on file change
UINT nID; // ID if message is WM_COMMAND
TCHAR szDir[_MAX_DRIVE + _MAX_DIR]; // Should really be variable length but it works (so far)
TCHAR szFile[_MAX_FNAME + _MAX_EXT]; // ditto
time_t nLastModTime; // Time of last modification (as returned by the _stat() function)
} THREADINFO;
typedef THREADINFO* PTHREADINFO;
void CNGFileChangeMonitor::CreateMonitoringThread( const CString& sPathName,
HWND hWnd,
UINT nMsg,
UINT nID,
int ePriority)
{
// Try to access file attributes
DWORD dwAttribs = ::GetFileAttributes(sPathName);
struct _stat FileInfo;
if ( (0xffffffff != dwAttribs) && (0 == _stat(sPathName, &FileInfo)) )
{
PTHREADINFO pThreadInfo = new THREADINFO; // Thread will delete this for us
pThreadInfo->hEvent = m_Event.m_hObject;
pThreadInfo->hWnd = hWnd;
pThreadInfo->nMsg = nMsg;
pThreadInfo->nID = nID;
pThreadInfo->nLastModTime = 0;
pThreadInfo->szDir[0] = '\0';
pThreadInfo->szFile[0] = '\0';
CString sDir = sPathName; // Target directory/file
CString sFile;
if (FILE_ATTRIBUTE_DIRECTORY & dwAttribs)
{
// Given path is a directory
}
else
{
// Given path is a file
// Break pathname down into directory and file names,
TCHAR szDrive[_MAX_DRIVE];
TCHAR szDir[_MAX_DIR];
TCHAR szFname[_MAX_FNAME];
TCHAR szExt[_MAX_EXT];
_splitpath(sPathName, szDrive, szDir, szFname, szExt );
sDir = CString(szDrive) + szDir; // Target directory
sFile = CString(szFname) + szExt; // Target file
// Get the time of last modification of the file
// This will be passed to the thread function so it can tell if
// the file has been changed when a directory change notification
// is received
pThreadInfo->nLastModTime = FileInfo.st_mtime;
}
// Update the relevant fields in the THREADINFO structure
strcpy(pThreadInfo->szDir, sDir);
strcpy(pThreadInfo->szFile, sFile);
// Create a worker thread to snoop on the file/directory
// First store the address of the structure in a map so
// that ThreadFunc (which is a static) can work out
// which CNGFileChangeMonitor object to pass the call to
g_ThreadParamMap.SetAt( (LPVOID)pThreadInfo, (LPVOID)this );
CWinThread* pThread = AfxBeginThread( CNGFileChangeMonitor::ThreadFunc,
(LPVOID)pThreadInfo,
ePriority);
if (pThread != NULL)
{
pThread->m_bAutoDelete = FALSE;
m_hThread = pThread->m_hThread;
m_pThread = pThread;
m_sMonitoredPath = sFile;
TRACE1("CNGFileChangeMonitor: Starting notification on %s\n", sFile);
}
else
{
g_ThreadParamMap.RemoveKey( (LPVOID)pThreadInfo );
delete pThreadInfo; // Thread creation failed so we must
} // delete the THREADINFO struct ourselves
if (m_hThread == NULL)
{
delete m_pThread;
m_pThread = NULL;
TRACE1("CNGFileChangeMonitor: Failed to start notification on %s\n", sFile);
}
}
}
void CNGFileChangeMonitor::DestroyMonitoringThread(void)
{
// Kill file change notification thread
if (m_pThread)
{
m_Event.SetEvent();
if (WAIT_FAILED != ::WaitForMultipleObjects(1,
&m_hThread,
TRUE,
INFINITE) )
{
delete m_pThread;
m_pThread = NULL;
m_hThread = NULL;
m_sMonitoredPath = "";
}
else
{
TRACE0("CNGFileChangeMonitor: Unable to destroy thread - wait failed\n");
}
}
}
/////////////////////////////////////////////////////////////////////////
// Worker thread function for detecting file changes
UINT CNGFileChangeMonitor::ThreadFunc(LPVOID pParam)
{
// Lookup corresponding thread object
CNGFileChangeMonitor* pThread = NULL;
if ( (!g_ThreadParamMap.Lookup(pParam, (LPVOID&)pThread)) || (!pThread) )
{
return 0;
}
g_ThreadParamMap.RemoveKey(pParam);
return pThread->DoThreadFunc(pParam);
}
UINT CNGFileChangeMonitor::DoThreadFunc(LPVOID pParam)
{
PTHREADINFO pThreadInfo = (PTHREADINFO) pParam;
// Extract parameters from thread info structure
CString sDir = pThreadInfo->szDir;
CString sFile = pThreadInfo->szFile;
HANDLE hEvent = pThreadInfo->hEvent;
HWND hWnd = pThreadInfo->hWnd;
UINT nMsg = pThreadInfo->nMsg;
UINT nID = pThreadInfo->nID;
time_t nLastModTime = pThreadInfo->nLastModTime;
// Full pathname (directory and file)
TCHAR szPath[_MAX_PATH];
strcpy(szPath, sDir);
strcat(szPath, sFile);
// Kill THREADINFO structure now we've got our stuff
delete pThreadInfo;
pThreadInfo = NULL;
// Nobody to notify - don't proceed further
if (hWnd == NULL)
{
return 0;
}
// Get a handle to a file change notification object.
HANDLE hChange = ::FindFirstChangeNotification( sDir,
FALSE,
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_SIZE);
// Return now if ::FindFirstChangeNotification() failed.
if (INVALID_HANDLE_VALUE == hChange)
{
return (1);
}
// Create an array of events we will respond to
HANDLE aHandles[2];
aHandles[0] = hChange; // The first event is the file change notification...
aHandles[1] = hEvent; // ...and the second tells the thread its time to die...
// Sleep until a file change notification wakes this thread or
// hEvent (m_Event) becomes set indicating it's time for the thread to end.
while (TRUE)
{
// Respond to a change notification
if (WAIT_OBJECT_0 == ::WaitForMultipleObjects (2, aHandles, FALSE, INFINITE))
{
// Directory change detected.
// If a filename was not given and the target window is still there,
// post a message to let the user know
if (!::IsWindow(hWnd))
{
break; // Stop monitoring - target window gone
}
if (sFile.IsEmpty()) // Monitoring change on directory
{
OnFileChange(sDir, hWnd, nMsg, nID);
}
else // Monitoring change on file
{ // so need to go through the directory and
// see if the file has changed
// Get the time of last modification of the file
// and see if it has changed since the last notification
// (or that at the time the thread was created if this is the first)
struct _stat FileInfo;
if (0 == _stat(szPath, &FileInfo)) // _stat() returns 0 if OK
{
// If last modification time is different, file has changed
// so post a message to the target window and store the new time
if (FileInfo.st_mtime != nLastModTime)
{
OnFileChange(szPath, hWnd, nMsg, nID);
nLastModTime = FileInfo.st_mtime;
}
}
}
::FindNextChangeNotification(hChange);
}
else // Kill this thread (m_Event became signaled)
{
break;
}
}
// Close the file change notification handle and return
::FindCloseChangeNotification(hChange);
return 0;
}
BOOL CNGFileChangeMonitor::OnFileChange(const CString& sPath, HWND hWnd, UINT nMsg, UINT nID)
{
UNREFERENCED_PARAMETER(sPath);
ASSERT(::IsWindow(hWnd) );
if (::IsWindow(hWnd) )
{
switch (nMsg)
{
case WM_COMMAND: // WM_COMMAND pass back nID
return ::PostMessage(hWnd, nMsg, nID, 0UL);
default: // For WM_USER message pass back the monitored path
// path as the LPARAM. This should be safe since
// m_sMonitoredPath is a class member
return ::PostMessage(hWnd, nMsg, 0, (LPARAM)(LPCTSTR)m_sMonitoredPath);
}
}
return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// CNGFileChangeMonitor interface
/******************************************************************************
* Start monitoring a file or directory for changes
*
******************************************************************************/
BOOL CNGFileChangeMonitor::Start(const CString& sPathName,
CWnd* pWnd,
UINT nMsg,
UINT nID,
int ePriority)
{
if (pWnd != NULL)
{
return Start( sPathName,
pWnd->GetSafeHwnd(),
nMsg,
nID,
ePriority);
}
return FALSE;
}
BOOL CNGFileChangeMonitor::Start(const CString& sPathName,
HWND hWnd,
UINT nMsg,
UINT nID,
int ePriority)
{
if (hWnd != NULL)
{
if (m_pThread != NULL)
{
Stop();
}
CreateMonitoringThread( sPathName,
hWnd,
nMsg,
nID,
ePriority);
return (NULL != m_hThread);
}
return FALSE;
}
/******************************************************************************
* Stop monitoring a file or directory for changes
*
******************************************************************************/
BOOL CNGFileChangeMonitor::Stop(void)
{
if (m_pThread != NULL)
{
DestroyMonitoringThread();
return TRUE;
}
return FALSE;
}