Table of contents
- Introduction
- Class Specification
- Class Details
Introduction
I have been working on utilities to enumerate files and folders. Initially, I had used the MSDN sample of using CFileFind
including the bWorking
variable. Things started to get complicated and messy once I needed to use similar functions and also to perform actions on some file extensions, e.g. (*.cpp, *.h).
I searched the internet and found an article in CodeProject by Rob Manderson [1] which almost met my needs. His re-usable CEnumerateFolders
class is the basis of my CEnumerateFiles
class.
Class specification
I have set the following specification for my enumerate class so that I can easily-reuse it.
- Easily derived.
- Able to perform action on combined file extensions such as *.cpp,*.h, *.dsw. No additional recursion should be done on the same directory on different file extensions, so as to speed up operation.
- Able to perform action on the directory itself after certain action has been done on the files. Example, all files in a directory have been deleted and the directory itself need to be deleted.
CEnumerateFiles
class CEnumerateFiles
{
public:
virtual void Execute(LPCTSTR lpzDir,
LPCTSTR lpzExt, bool bRecurse=true);
CEnumerateFiles();
virtual ~CEnumerateFiles();
protected:
CMatchFileExtension m_MatchFile;
virtual void ExecuteDirectory(LPCTSTR lpzDir);
virtual void OnFile(LPCTSTR lpzFile)=0;
};
The CEnumerateFiles
class can be easily derived by making the functions Execute
, OnFile
and ExecuteDirectory
virtual. Notice that OnFile
is a pure virtual function. This is so as I require derived classes to perform specific actions on the files.
The details of Execution
function is as follows:
void CEnumerateFiles::Execute(LPCTSTR lpzDir,LPCTSTR lpzExt, bool bRecurse)
{
CFileFind FindAllFile;
CString sFileName(lpzDir);
sFileName+="\\*.*";
BOOL bFileExist=FindAllFile.FindFile(sFileName);
while(bFileExist)
{
bFileExist=FindAllFile.FindNextFile();
if(FindAllFile.IsDots())
continue;
if(FindAllFile.IsDirectory() && bRecurse)
{
Execute(FindAllFile.GetFilePath(),lpzExt,bRecurse);
continue;
}
if(m_MatchFile.IsMatchExtension(FindAllFile.GetFilePath(),lpzExt))
OnFile(FindAllFile.GetFilePath());
}
FindAllFile.Close();
ExecuteDirectory(lpzDir);
}
Note:
- No file extension is passed down in
FindFile
function. Instead a wildcard (*.*) is used. The reason is that if a file extension is specified in the FindFile
function and the current directory traversel does not contain the specified file extension, the while
loop will not be executed and no file operation will be performed.
ExecuteDirectory
is called only after the while
loop. I require an operation to be done on the directory argument lpzDir
specified in the Execute
function. If the following code snippet is used:
if(FindAllFile.IsDirectory() && bRecurse)
{
Execute(FindAllFile.GetFilePath(),lpzExt,bRecurse);
ExecuteDirectory(FindAllFile.GetRoot());
continue;
}
no operation will be done on the directory if a programmer passes down the following directory argument: D:\Project\Test\Debug and there is no more directory in D:\Project\Test\Debug.
- File operation will only be done in the
OnFile
function if matching file extensions are found.
CMatchFileExtension
CMatchFileExtension
class' main task is to separate multiple file extensions separated by commas into individual file extensions. An example is as follows:
*.dsw,*.cpp,*.h ---> *.dsw
*.cpp
*.h
strtok
function is used in CMatchFile::IsMatchExtension(LPCTSTR lpzFile, LPCTSTR lpzExt)
to separate the file extensions. Below is the details of IsMatchExtension
function. Notice, that the function will return true
if the extension argument is *.*
.
bool CMatchFileExtension::IsMatchExtension(LPCTSTR lpzFile, LPCTSTR lpzExt)
{
strcpy(m_ctemp,lpzExt);
char *token;
token = strtok( m_ctemp, "," );
m_sArrayExt.RemoveAll();
while(token!=NULL)
{
m_sArrayExt.Add(token);
token = strtok( NULL,"," );
}
bool bFound=false;
for (int i=0;i<m_sArrayExt.GetSize();i++)
{
m_sCompare=m_sArrayExt[i];
if(!m_sCompare.CompareNoCase("*.*"))
{
bFound=true;
break;
}
m_sCompare=GetFileExtension(m_sCompare);
if(!m_sCompare.CompareNoCase(GetFileExtension(lpzFile)))
{
bFound=true;
break;
}
}
return bFound;
}
Deriving a class from CEnumerateFiles
For a real life sample, I have created a class which deletes files and folders. Deriving a class is easy. I just need to implement the OnFile
function and the ExecuteDirectory
(if required). Below is the header file of the sample derived class:
class CDeleteDirectoryNFiles : public CEnumerateFiles
{
public:
CDeleteDirectoryNFiles();
virtual ~CDeleteDirectoryNFiles();
protected:
virtual void ExecuteDirectory(LPCTSTR lpzDir);
virtual void SetFileToNotReadOnlyStatus(LPCTSTR lpzFile);
virtual void OnFile(LPCTSTR lpzFile);
CFileStatus m_FileStatus;
};
and below is a code snippet of the derived class implementation:
void CDeleteDirectoryNFiles::ExecuteDirectory(LPCTSTR lpzDir)
{
for(;;)
{
if(!::RemoveDirectory(lpzDir))
{
CString s;
s.Format("Not able to delete directory %s due to"
" %sClick Yes to Retry.Click No to continue the "
"deleting process",lpzDir,CErrorMsg::GetErrorString());
int iRes=AfxMessageBox(s,MB_YESNO|MB_ICONEXCLAMATION);
if(iRes==IDNO)
break;
else
continue;
}
TRACE("Deleted Directory %s\n",lpzDir);
break;
}
}
void CDeleteDirectoryNFiles::OnFile(LPCTSTR lpzFile)
{
SetFileToNotReadOnlyStatus(lpzFile);
for(;;)
{
if(!::DeleteFile(lpzFile))
{
CString s;
s.Format("Not able to delete file %s due to"
" %sClick Yes to Retry.Click No to delete other files",
lpzFile,CErrorMsg::GetErrorString());
Beep(3000,500);
int iRes=AfxMessageBox(s,MB_YESNO |MB_ICONEXCLAMATION);
if(iRes==IDNO)
{
return;
}
else
continue;
}
break;
}
TRACE("Deleted File %s\n",lpzFile);
}
void CDeleteDirectoryNFiles::SetFileToNotReadOnlyStatus(LPCTSTR lpzFile)
{
CFile::GetStatus( lpzFile,m_FileStatus);
m_FileStatus.m_attribute=0x00;
CFile::SetStatus(lpzFile,m_FileStatus);
}
Using the derived class
To use the derived class, simply instantiate the derived class and call the Execute
function supplying directory and combined file extensions argument. An example is as follows:
{
CDeleteDirectoryNFiles Dlt;
CString sDir="C:\\TestDelete";
Dlt.Execute(sDir,"*.dsw,*.dsp,*.cpp,*.h");
}
Note: The above code snippet is just a sample of using the derived class. It will not work correctly if there is another file extension. Also, please don't place any important VC6 source code in C:\TestDelete when trying the code snippet.
Reference
- A class to make it easy to enumerate folder contents, by Rob Manderson (CodeProject).
History
- 5-July-2005 - First release.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.