Click here to Skip to main content
15,884,177 members
Articles / Desktop Programming / ATL

File and Directory Enumeration

Rate me:
Please Sign up or sign in to vote.
4.61/5 (15 votes)
3 Mar 2003Ms-PL4 min read 204K   4.6K   62  
Template based file and directory enumeration class.
#pragma once

#if !defined(_RECURSEDIR_INCLUDED_)
#   define _RECURSEDIR_INCLUDED_

#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

#   ifndef lenof
#      define lenof(x)	  (sizeof(x) / sizeof((x)[0]))
#   endif

//
// in whatever context you use this class, you have to define the TRACE function
//
#if defined(_AFX)
#	define RDTRACE	TRACE
#elif defined(_ATL)
#	define RDTRACE	ATLTRACE
#else
#	define RDTRACE	_rdtrace
// no MFC, no ATL, define it ourselves and use ODS
#include <stdio.h>
#include <stdarg.h>

void _rdtrace(LPCTSTR fmt, ... )
{
	TCHAR buf[2000];
	va_list args;
	
	va_start( args, fmt );
	_vsntprintf( buf, sizeof buf - 1, fmt, args );
	va_end( args );
	
	::OutputDebugString(buf);
}

#endif

#if defined(_AFX)
//
// we use CString and CArray
//
#else
	//
	// include the string class from the STL
	//
	#pragma warning(push, 2)
	#include <string>
	#include <vector>
	using std::string;
	using std::vector;
	#pragma warning(pop)
#endif

//
// error handling constants
//
#   define RDEH_CONTINUE		   0x0000
#   define RDEH_CONTINUENEXTFILE   0x0001
#   define RDEH_CONTINUENEXTDIR	   0x0002
#   define RDEH_STOP			   0x0004
#   define RDEH_ABORT			   0x0008
#   define RDEH_FAIL			   0x0010

//
// location constants
//
#   define RDLOC_RECURSEDIR		   2
#   define RDLOC_RECURSEFILE	   3
#   define RDLOC_RECURSEFILE_SKIP  4
#   define RDLOC_HANDLERAWFILE	   6
#   define RDLOC_HANDLEFILE		   7
#   define RDLOC_HANDLEERROR	   8

//////////////////////////////////////////////////////////////////////////
//
// Simple structure to hold a filename
//
typedef struct tagSimpleFile 
{
#if defined(_AFX)
	CString		name;
#else
	string		name;
#endif

	struct tagSimpleFile& operator=(const WIN32_FIND_DATA& r)
	{
		name = r.cFileName;
		return *this;
	}
} SIMPLE_FILE;

//////////////////////////////////////////////////////////////////////////
//
// template based directory recursion
//
template<class T>
class CRecurseDir
{
public:
	CRecurseDir(LPCTSTR pstrMask = NULL) :
		  m_dwLastError(ERROR_SUCCESS),
		  m_nFiles(0),
		  m_nDirs(0),
		  m_pstrMask(pstrMask)
	  {
		  m_hStopEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
		  if(m_hStopEvent == NULL)
		  {
			  RDTRACE(_T("Could not create event."));
		  }
		  m_bOwnEvent = true;
		  
		  if(m_pstrMask == NULL)
			  m_pstrMask = _T("*");
	  }


	virtual	~CRecurseDir()
	{
		if(m_bOwnEvent)
			::CloseHandle(m_hStopEvent);

		m_hStopEvent = NULL;
	}


	// /////////////////////////////////////////////////////////////////////////////
	// called to handle a certain error, return one of the RDEH_
	virtual DWORD HandleError(DWORD dwLocation, DWORD dwErr, LPCTSTR pstrInfo = NULL)
	{
		TCHAR	szErr[4096];
		//////////////////
		
		::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), szErr, 4096, NULL);
		
		Log(dwLocation, szErr, pstrInfo);
		
		return RDEH_CONTINUENEXTFILE;
	}
	
	// called to log an event
	virtual void Log(DWORD dwLocation, LPCTSTR pstrFile, LPCTSTR pstrInfo = NULL)
	{
		if(pstrInfo)
			RDTRACE(_T("Location: %i, %s (%s)\r\n"), dwLocation, pstrFile, pstrInfo);
		else
			RDTRACE(_T("Location: %i, %s\r\n"), dwLocation, pstrFile);
	}
	
	// called to handle a certain file
	virtual	bool HandleRawFile(LPCTSTR pstrFile)
	{
		//
		// this is called before the file is processed and before HandleFile() is
		// called. if this returns false, the file is not processed at all
		
		Log(RDLOC_HANDLERAWFILE, pstrFile);
		
		//
		// if you add archive detection/extraction add extraction here and return
		// false after walking the tree
		//
		
		return true;
	}

	// called to handle a certain file 
	virtual void HandleFile(T* /*pFile*/)
	{
		// Log(RDLOC_HANDLEFILE, pFile->name.c_str());
	}
	
	// /////////////////////////////////////////////////////////////////////////////
	virtual	bool Run(LPCTSTR pstrDir)
	{
		return WalkTree(pstrDir);
	}
	

	// ///////////////////////////////////////////////////////////////////////////////
	// use to exclude directories from scanning
	virtual	bool CheckUseDir(LPCTSTR /*pstrPath*/, WIN32_FIND_DATA* /*pwfd*/)
	{
		return true;	// use it
	}
	
	// use to exclude files from scanning
	virtual	bool CheckUseFile(LPCTSTR /*pstrPath*/, WIN32_FIND_DATA* /*pwfd*/)
	{
		return true;	// use it
	}
	
	// called when recursion through a dir is completed, 
	// dir is not touched afterwards anymore
	virtual	void FinishedDir(LPCTSTR /*pstrDir*/)
	{
	}

	// returns the currently processed file
	T& GetCurrentFile(void)
	{
		return m_CurrentFile;
	}

	// returns the number of already processed directories (unfiltered)
	int GetDirCount(void) const
	{
		return m_nDirs;
	}

	// returns the number of already processed files (unfiltered)
	int GetFileCount(void) const
	{
		return m_nFiles;
	}

	// set the current file/dir counters to zero
	void ResetCounters(void)
	{
		m_nDirs = 0;
		m_nFiles = 0;
	}

	// set the stop event. if INVALID_HANDLE_VALUE is given, the object creates a temp event.
	void SetEvent(HANDLE hEvent = INVALID_HANDLE_VALUE)
	{
		if(m_bOwnEvent)
		{
			::CloseHandle(m_hStopEvent);
			m_bOwnEvent = FALSE;
		}
		
		if(hEvent == INVALID_HANDLE_VALUE)
		{
			hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
			m_bOwnEvent = true;
		}
		
		m_hStopEvent = hEvent;
	}

protected:
	bool WalkTree(LPCTSTR pstrDir)
	{
		WIN32_FIND_DATA wfd;
		HANDLE			hFind;
		TCHAR			szPath[MAX_PATH];
		bool			bRecurse = true;
		_tcsncpy(szPath, pstrDir, MAX_PATH);
		//////////////////////////////////

		::PathAppend(szPath, _T("*"));

		m_nDirs++;	// count this directory

		Log(RDLOC_RECURSEDIR, pstrDir);

		// Iterate through dirs

		hFind = ::FindFirstFile(szPath, &wfd);
		if(hFind != INVALID_HANDLE_VALUE)
		{
			do
			{

				// FIRST check if its a dir

				if(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{

					// if not a DOT, it will not even do the string compare

					if(!((wfd.cFileName[0] == '.') && ((wfd.cFileName[1] == '.') || (wfd.cFileName[1] == '\0'))))
					{
						if(bRecurse)
						{
							// Recurse Dirs

							if(CheckUseDir(pstrDir, &wfd))
							{
								TCHAR szNextPath[MAX_PATH];
								::PathCombine(szNextPath, pstrDir, wfd.cFileName);

								WalkTree(szNextPath);
							}
							else
							{
								Log(RDLOC_RECURSEDIR, pstrDir, _T("skipping directory"));
							}
						}
					}
				}
			} while((::WaitForSingleObject(m_hStopEvent, 0) != WAIT_OBJECT_0) && (::FindNextFile(hFind, &wfd)));
			::FindClose(hFind);
		}
		else
		{
			m_dwLastError = ::GetLastError();
			switch(HandleError(RDLOC_RECURSEDIR, m_dwLastError, pstrDir))
			{
				case RDEH_CONTINUE:
				case RDEH_CONTINUENEXTFILE:
					break;

				case RDEH_CONTINUENEXTDIR:
					return true;

				default:
					return false;
			}
		}

		// Iterate through files

		if(::WaitForSingleObject(m_hStopEvent, 0) != WAIT_OBJECT_0)
		{
			hFind = ::FindFirstFile(szPath, &wfd);
			if(hFind != INVALID_HANDLE_VALUE)
			{
				do
				{

					if((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
					{
						m_nFiles++;

						if(CheckUseFile(pstrDir, &wfd))
						{
							TCHAR szNextFile[MAX_PATH];
							_tcsncpy(szNextFile, pstrDir, MAX_PATH);
							::PathAppend(szNextFile, wfd.cFileName);

							// handle the filename, e.g. extract archives

							if(HandleRawFile(szNextFile))
							{
								m_CurrentFile = wfd;
								HandleFile(&m_CurrentFile);
							}
							else
							{
								Log(RDLOC_RECURSEFILE_SKIP, szNextFile);
							}
						}
					}
				} while((::WaitForSingleObject(m_hStopEvent, 0) != WAIT_OBJECT_0) && (::FindNextFile(hFind, &wfd)));
				::FindClose(hFind);
			}
			else
			{
				m_dwLastError = ::GetLastError();
				switch(HandleError(RDLOC_RECURSEFILE, m_dwLastError, pstrDir))
				{
					case RDEH_CONTINUE:
					case RDEH_CONTINUENEXTFILE:
					case RDEH_CONTINUENEXTDIR:
						return true;

					default:
						return false;
				}
			}

			FinishedDir(pstrDir);
		}
		else
		{
			Log(RDLOC_RECURSEFILE, _T("aborted"));
		}

		return true;
	}

	// set the event to stop enumeration
	void	CancelRun(void)
	{
		::SetEvent(m_hStopEvent);
	}

private:
	DWORD	m_dwLastError;

	T		m_CurrentFile;

	int		m_nFiles;			// number of processed files
	int		m_nDirs;			// number of processed dirs

	HANDLE	m_hStopEvent;		// event for stopping enumeration
	bool	m_bOwnEvent;		// true if the object owns the event, thus deletes it in the dtor
	LPCTSTR m_pstrMask;
};

//////////////////////////////////////////////////////////////////////////
//
// sample implementation: recursively delete a directory and all files within
//
class CCleanDir : public CRecurseDir<struct tagSimpleFile>
{
public:
	bool	Run(LPCTSTR pstrDir)
	{
		RDTRACE(_T("Nuking directory %s"), pstrDir);
		return CRecurseDir<struct tagSimpleFile>::Run(pstrDir);
	}
	
	DWORD HandleError(DWORD /*dwLocation*/, DWORD /*dwErr*/, LPCTSTR /*pstrInfo*/)
	{
		return RDEH_CONTINUE;
	}
	
	bool HandleRawFile(LPCTSTR pstrFile)
	{
		if(::SetFileAttributes(pstrFile, FILE_ATTRIBUTE_NORMAL))
		{
			if(!::DeleteFile(pstrFile))
			{
				RDTRACE(_T("could not delete %s"), pstrFile);
			}
		}
		else
		{
			RDTRACE(_T("could not set file attributes on %s"), pstrFile);
		}
		
		return false; // dont use the file anymore
	}
	
	void FinishedDir(LPCTSTR pstrDir)
	{
		if(::SetFileAttributes(pstrDir, NULL))
		{
			if(!::RemoveDirectory(pstrDir))
			{
				RDTRACE(_T("could not remove directory %s"), pstrDir);
			}		
		}
		else
		{
			RDTRACE(_T("could not set file attributes on %s"), pstrDir);
		}
		
	}
};

class CDirectoryContent : public CRecurseDir<WIN32_FIND_DATA>
{
	// define a appropriate array type
#if defined(_AFX)
	typedef CArray<WIN32_FIND_DATA,WIN32_FIND_DATA>	arraytype;
#else
	typedef std::vector<WIN32_FIND_DATA>	arraytype;
#endif

public:
	DWORD HandleError(DWORD /*dwLocation*/, DWORD /*dwErr*/, LPCTSTR /*pstrInfo*/)
	{
		return RDEH_CONTINUE;	// ignore any error
	}

	virtual bool CheckUseDir(LPCTSTR, WIN32_FIND_DATA*)
	{
		return false;	// we do not recurse
	}

	void HandleFile(WIN32_FIND_DATA* pFile)
	{
#if defined(_AFX)
		m_list.Add(*pFile);
#else
		m_list.push_back(*pFile);
#endif
	}

	arraytype& List()
	{
		return m_list;
	}

protected:
	arraytype m_list;
};

#endif // !defined(_RECURSEDIR_INCLUDED_)

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 Microsoft Public License (Ms-PL)


Written By
Software Developer (Senior)
Portugal Portugal
Software Smith, Blacksmith, Repeat Founder, Austrian, Asgardian.

Comments and Discussions