Click here to Skip to main content
15,867,756 members
Articles / Desktop Programming / MFC
Article

Enumerate files and folders with combined file extensions mask

Rate me:
Please Sign up or sign in to vote.
3.00/5 (7 votes)
7 Jul 20053 min read 42K   1K   28   1
Reusable class to enumerate files and folders with combined file extensions, example: *.cpp,*.h,*.dsw.

Table of contents

  1. Introduction
  2. Class Specification
  3. 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+="\\*.*"; // to traverse to next directory 

    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:

  1. 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.
  2. 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.

  3. 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:

{
    //some function
    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

  1. A class to make it easy to enumerate folder contents, by Rob Manderson (CodeProject).

History

  • 5-July-2005 - First release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Engineer
Singapore Singapore
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
General[Message Removed] Pin
immetoz1-Oct-08 9:39
immetoz1-Oct-08 9:39 
Spam message removed

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.