Click here to Skip to main content
15,861,168 members
Articles / Desktop Programming / MFC

Resource ID Organiser Add-In for Visual C++ 5.0/6.0/.NET

Rate me:
Please Sign up or sign in to vote.
4.98/5 (71 votes)
10 Jan 2005CPOL25 min read 527.5K   12.1K   201  
An application/add-in to organise and renumber resource symbol IDs
////////////////////////////////////////////////////////////////
// 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;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Riverblade Limited
United Kingdom United Kingdom
I haven't always written software for a living. When I graduated from Surrey University in 1989, it was with an Electronic Engineering degree, but unfortunately that never really gave me the opportunity to do anything particularly interesting (with the possible exception of designing Darth Vader's Codpiece * for the UK Army in 1990).
    * Also known as the Standard Army Bootswitch. But that's another story...
Since the opportunity arose to lead a software team developing C++ software for Avionic Test Systems in 1996, I've not looked back. More recently I've been involved in the development of subsea acoustic navigation systems, digital TV broadcast systems, port security/tracking systems, and most recently software development tools with my own company, Riverblade Ltd.

One of my personal specialities is IDE plug-in development. ResOrg was my first attempt at a plug-in, but my day to day work is with Visual Lint, an interactive code analysis tool environment with works within the Visual Studio and Eclipse IDEs or on build servers.

I love lots of things, but particularly music, photography and anything connected with history or engineering. I despise ignorant, intolerant and obstructive people - and it shows...I can be a bolshy cow if you wind me up the wrong way...Laugh | :laugh:

I'm currently based 15 minutes walk from the beach in Bournemouth on the south coast of England. Since I moved here I've grown to love the place - even if it is full of grockles in Summer!

Comments and Discussions