#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_)