Click here to Skip to main content
Click here to Skip to main content

Simple File Search Class

, 30 Jun 2010
Rate this:
Please Sign up or sign in to vote.
A simple search class to search a directory and all sub-directories in the background for files with a defined search pattern.

Introduction 

This is a class I created to recursively search a directory and all sub-directories.

Background

I needed to search for all files in a chosen directory and sub-directories and display them in a list box. But I also needed my Form to be responsive and tell the user that it's working. So I built a class which accepts a BackgroundWorker from a form and passes back detailed progress information to the form.

Using the Code

Basically, there are a bunch of counters, a "UserState" struct to hold all status information to be passed back to the form, some properties, and the Search method does all the heavy lifting.

There's also an EstimateNumSubDirectories function which loops through all sub-directories up to 3 levels deep. This is so that the percent complete passed back to the BackgroundWorker.ReportProgress method won't always be nearly 100%.

The Search Method

NOTE: This could be MUCH more easily done by using "System.IO.Directory.GetFiles(directoryToSearch, searchPattern, System.IO.SearchOption.AllDirectories)". The SearchOption.AllDirectories does all of the recursive stuff for you, but it doesn't send back update progress reports on a BackgroundWorker so this didn't work out for me.

Basically it checks to see if the passed directory exists, makes sure the search pattern isn't empty, if a BackgroundWorker is being used, it checks to see if you've cancelled processing, and if this is the first pass in the loop it runs the EstimateNumSubDirectories method.

It then searches the directory passed to it for any files with the search pattern also passed to it, gets a list of sub-directories, and each sub-directory gets passed back into the method for another pass until there are no sub-directories left. So it will go all the way down a tree, and then keep going up and down the tree until no branches are left.

IMPORTANT: Since the EstimateNumSubDirectories method has already got the number of directories found 3 levels deep, the Search method only adds to the number of directories found if you're more than 3 levels deep.

Here's the code:

/// <summary>
/// Searches the specified directory.
/// </summary>
/// <param name="directoryToSearch">The directory path to search.</param>
/// <param name="searchPattern">The search pattern to use, e.g. "*.doc"</param>
public void Search(string directoryToSearch, string searchPattern)
// recursively look in every subdirectory under the directory passed for documents
{
    // check data input
    if (!System.IO.Directory.Exists(directoryToSearch))
    {
        MessageBox.Show("The directory: " + directoryToSearch + " doesn't exist.",
            "Directory Not Found", MessageBoxButtons.OK, MessageBoxIcon.Stop);
        return;
    }

    if (searchPattern == string.Empty)
    {
        MessageBox.Show("You must enter a search pattern.", "No Search Pattern",
            MessageBoxButtons.OK, MessageBoxIcon.Stop);
        return;
    }

    // check to see if the user hit the Cancel button
    if (_bw != null && _bw.CancellationPending)
    {
        //bail out
        return;
    }

    /// if this is the first loop, you need to estimate how many directories
    /// you will have to search
    if (DirectoriesFound == 0)
    {
        /// if the BackGround worker has been specified and supports updating
        /// send a message in the userstate that you are estimating remaining time
        if (_bw != null && _bw.WorkerReportsProgress)
        {
            UserState us = new UserState(Operations.Estimating);
            _bw.ReportProgress(bwPercentComplete, us);
        }

        // estimate number of sub-directories up to 2 levels deep
        EstimateNumSubDirectories(directoryToSearch);
        _CurrentNumDirectoryLevels = 0;

        if (_bw != null && _bw.WorkerReportsProgress)
        {
            UserState us = new UserState(Operations.Searching, directoryToSearch);
            _bw.ReportProgress(bwPercentComplete, us);
        }
    }

    try
    {
        /// use the GetFiles method to search the provided directory for 
        /// a file with "*.doc" in the filename and process every file found
        foreach (string fileName in System.IO.Directory.GetFiles
				(directoryToSearch, searchPattern))
        {
            // increment the number of files found
            _NumFilesFound++;

            /// if you can, send each filename back to the BackgroundWorker's
            /// ProgressChanged event method via the UserState. Otherwise,
            /// create a list of file names.
            if (_bw != null && _bw.WorkerReportsProgress)
            {
                UserState us = new UserState
		(Operations.Searching, directoryToSearch, fileName);
                _bw.ReportProgress(bwPercentComplete, us);
            }
            else
            {
                // add every found file to the list box
                _FoundFileNames.Add(fileName);
            }
        }

        // this directory has been searched, increment counters
        //_NumDirectoryLevels++;
        // not counting the top level, increment the number of directories processed
        if (_CurrentNumDirectoryLevels > 0)
            _NumDirectoriesProcessed++;

        // if you can, report back to backgroundworker
        if (_bw != null && _bw.WorkerReportsProgress && _NumDirectoriesFound != 0)
        {
            // report progress back to form after every document has been processed
            bwPercentComplete = (int)Math.Round(((decimal)_NumDirectoriesProcessed / 
				(decimal)_NumDirectoriesFound) * 100);
            UserState us = new UserState(Operations.Searching, directoryToSearch);
            _bw.ReportProgress(bwPercentComplete, us);
        }

        // get the subdirectories
        string[] dirsFound = System.IO.Directory.GetDirectories(directoryToSearch);

        // if you are in at least two levels, add to the initial estimate of directories
        if (_CurrentNumDirectoryLevels > _NumDirectoryLevelsToEstimate)
        {
            _NumDirectoriesFound += dirsFound.GetLength(0);
        }

        foreach (string subDir in dirsFound)
        {
            _CurrentNumDirectoryLevels++;
            Search(subDir, searchPattern);
        }

        _CurrentNumDirectoryLevels -= 1;
    }
    catch (System.UnauthorizedAccessException)
    {
        // can't look in unauthorized directory. just ignore and do nothing
        _NumDirectoriesFound -= 1;
        _CurrentNumDirectoryLevels -= 1;
    }
}

The EstimateNumSubDirectories method adds the number of sub-directories found under the directory passed but only 3 levels deep. The recursive part works the same as the Search method in that it works its way down the tree passing each sub-directory back into the method:

private void EstimateNumSubDirectories(string directoryToSearch)
// estimates the number of directories up to 3 levels deep
{
    try
    {
        if (_CurrentNumDirectoryLevels <= _NumDirectoryLevelsToEstimate)
        {
            string[] dirsFound = System.IO.Directory.GetDirectories(directoryToSearch);
            _NumDirectoriesFound += dirsFound.GetLength(0);

            foreach (string subDir in dirsFound)
            {
                _CurrentNumDirectoryLevels++;
                EstimateNumSubDirectories(subDir);
            }
            _CurrentNumDirectoryLevels -= 1;
        }
        else
        {
            _CurrentNumDirectoryLevels -= 1;
        }
    }
    catch (System.UnauthorizedAccessException)
    {
        // can't look in unauthorized directory. just ignore and do nothing
        _CurrentNumDirectoryLevels -= 1;
    }
}

The UserState struct contains different status messages, the current operation (estimating or searching), the directory currently being processed, if a file was just found the file path, and 3 constructors:

public struct UserState
{
    /// <summary>
    /// "Estimating" status message.
    /// </summary>
    public string estimatingMessage;
    /// <summary>
    /// "Searching" status message.
    /// </summary>
    public string searchingMessage;
    /// <summary>
    /// Name of the directory currently being processed.
    /// </summary>
    public string directoryInProcess;
    /// <summary>
    /// The type of the current operation.
    /// </summary>
    public Operations currentOperation;
    /// <summary>
    /// The file name of a found file.
    /// </summary>
    public string foundFileName;

    public UserState(Operations _currentOperation, string _directoryInProcess,
        string _foundFileName)
    // Structure constructor
    {
        estimatingMessage = "Estimating remaining time...";
        searchingMessage = "Searching for documents...";
        currentOperation = _currentOperation;
        directoryInProcess = _directoryInProcess;
        foundFileName = _foundFileName;
    }

    public UserState(Operations _currentOperation, string _directoryInProcess)
    // Structure constructor
    {
        estimatingMessage = "Estimating remaining time...";
        searchingMessage = "Searching for documents...";
        currentOperation = _currentOperation;
        directoryInProcess = _directoryInProcess;
        foundFileName = string.Empty;
    }

    public UserState(Operations _currentOperation)
    // Structure constructor
    {
        estimatingMessage = "Estimating remaining time...";
        searchingMessage = "Searching for documents...";
        currentOperation = _currentOperation;
        directoryInProcess = string.Empty;
        foundFileName = string.Empty;
    }
}

To use the status updates back on the form, you can convert the UserState back to the FileSearchClass.UserState as shown below:

if (e.UserState != null && e.UserState is FileSearchClass.UserState)
{
    FileSearchClass.UserState state = (FileSearchClass.UserState)e.UserState;

    switch (state.currentOperation)
    {
        case FileSearchClass.Operations.Estimating:
            this.xStatusLbl.Text = string.Format("Status: {0}", state.estimatingMessage);
            break;
        case FileSearchClass.Operations.Searching:
            this.xStatusLbl.Text = string.Format("Status: {0}", state.searchingMessage);
            if (state.foundFileName != string.Empty)
                {
                    this.xFilesListBox.Items.Add(state.foundFileName);
                    this.xFilesListBox.Refresh();
                }
            break;
    }
}

Points of Interest

Learning how to have a method loop back into itself was fun and could be useful in many scenarios. The main advantage to this solution is that it accepts a BackgroundWorker and can send back progress updates to the form.

Another interesting point for me was to get a progress bar to work back on the form. But the progress bar would be nearly full all the time because for every directory you find, it gets processed right away. Using the estimation technique, you can get a good idea of how many directories you'll need to process and add to your estimation until you're finished.

History

  • 2nd September, 2009: My first revision
  • 30th June, 2010: Article updated

License

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

About the Author

jabit
Software Developer Veracity
United States United States
I'm a .NET developer, fluent in C# and VB.NET with a focus on SharePoint and experience in WinForms, WPF, Silverlight, ASP.NET, SQL Server. My roots come from a support/system administrator role so I know my way around a server room as well.
 
I have a passion for technology and I love what I do.

Comments and Discussions

 
Questionretrieving data from UserState Pinmemberrick at abc29-Jun-10 13:20 
AnswerRe: retrieving data from UserState Pinmemberjabit29-Jun-10 14:32 
You have to convert it back to the UserState enum. This is how I did it:
 
if (e.UserState != null && e.UserState is FileSearchClass.UserState)
{
    FileSearchClass.UserState state = (FileSearchClass.UserState)e.UserState;
 
    switch (state.currentOperation)
    {
        case FileSearchClass.Operations.Estimating:
            this.xStatusLbl.Text = string.Format("Status: {0}", state.estimatingMessage);
            break;
        case FileSearchClass.Operations.Searching:
            this.xStatusLbl.Text = string.Format("Status: {0}", state.searchingMessage);
            if (state.foundFileName != string.Empty)
                {
                    this.xFilesListBox.Items.Add(state.foundFileName);
                    this.xFilesListBox.Refresh();
                }
            break;
    }
}

GeneralRe: retrieving data from UserState Pinmemberrick at abc29-Jun-10 17:41 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 30 Jun 2010
Article Copyright 2009 by jabit
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid