Click here to Skip to main content
15,886,873 members
Articles / Programming Languages / C#

Multithreaded File/Folder Finder

Rate me:
Please Sign up or sign in to vote.
4.19/5 (18 votes)
26 May 2010CPOL17 min read 110.2K   5.6K   125  
File Find is fast, especially if you have multiple physical drives; version 2.1.0.17.
//#define useRecurseFoldersOnly
//   Defining useRecurseFoldersOnly will cause the RecurseFoldersOnly routine to be called when
// searching for folder names only. This did not prove to be a significant time saver.
//   I elected to run with enumerators on networked disks as the default because enumerators were always fastest.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;            //for DirectoryInfo and FileInfo
using System.Threading;
using System.Windows.Forms;

namespace FileFind
{
    /// <summary>
    ///   One DiskScan thread is started for each physical disk drive.
    ///   DiskScan performs recursive scans of directories looking for file and/or directory matches.
    ///   Information is passed back to the main display via delegates provided at construction.
    ///   All errors and informational messages sent via SendMsg are preceded by a single
    /// space so the information may be sorted out from the files and directories that
    /// are found.
    /// </summary>
    class DiskScan
    {
        #region Local constants and variables
        // Reference to main form used to make syncronous user interface calls:
        private ContainerControl m_form;
        private Delegate DisplayMsg = null;
        private Delegate SendDirInfo = null;
        private Delegate SendFileInfo = null;
        private ManualResetEvent pleaseTerminate;
        private Delegate ThreadTerminating = null;

        private String disksToScan = null;  //Set before issuing RUN
            //Each entry contains a drive to scan; for example: "C:\"
        //following fields identify which folders and files to search for
        private String filesToFind = null;
        private String[] fileMasks = null;

        private List<Wildcard> wildCards = null;     //contains: Wildcard objects
        bool directoriesOnly = true;

        private enum searchingForType
        {
            searchingForTypeFile,
            searchingForTypeFolder,
            searchingForTypeBoth
        };
        private searchingForType searchingFor;

        private ConfigInfo configInfo;
        bool showingHiddenFolders;              //local copy of configInfo.ShowHiddenFoldersFiles
        bool scanViaEnumerator;                 //set true if configInfo.ScanningViaEnumerator is true
                                                //or DriveType.Network == driveInfo.DriveType

        private DateTimeInfo usingDateTimeRange;
        private DateTime
            fromFileTime,
            toFileTime;

        private string volumeId = null;         //currently processing this volume label
        private bool scanningSpecificPaths;     //Set to true if a rule specified a specific path
                                                //is to be searched. True will prevent testing for
                                                //exclusions on lower level directories.

        FilterRules filterRules = null;
        #endregion Local constants and variables

        #region Constructors
        public DiskScan(
            ContainerControl form,              //the user interface
            Delegate DsplyMsg,                  //send text messages
            Delegate SendDir,                   //send directory information
            Delegate SendFile,                  //send file information
            ManualResetEvent terminateRequest,  //receive a termination request
            Delegate ThreadStopping,            //send an end of thread message
            DateTimeInfo selectedDateTime       //provides date/time range information
            )
        {
            m_form = form;
            DisplayMsg = DsplyMsg;
            SendDirInfo = SendDir;
            SendFileInfo = SendFile;
            pleaseTerminate = terminateRequest;
            ThreadTerminating = ThreadStopping;
            usingDateTimeRange = selectedDateTime;
        }
        #endregion Constructors

        #region Properties
        public String SearchingDrives
        {
            get { return disksToScan; }
            set { disksToScan = value; }
        }

        public String IFindFiles
        { set { filesToFind = value; } }

        public bool SearchForFiles
        { set { searchingFor = DiskScan.searchingForType.searchingForTypeFile; } }

        public bool SearchForFolders
        { set { searchingFor = DiskScan.searchingForType.searchingForTypeFolder; } }

        public bool SearchForBoth
        { set { searchingFor = DiskScan.searchingForType.searchingForTypeBoth; } }
        #endregion Properties

        /// <summary>
        /// Insure preconditions are met before searching the assigned disk(s)
        /// Catch exceptions to insure the ThreadTerminating message is sent
        /// </summary>
        public void Run()
        {
            try
            {
                if (DateTimeInfo.DateTimeRangeSearch.DateTimeRangeOff != usingDateTimeRange.IDateTimeRange)
                {
                    fromFileTime = usingDateTimeRange.IFromDateTime;
                    toFileTime = usingDateTimeRange.IToDateTime;
                }
                configInfo = new ConfigInfo();
                if ((DiskScan.searchingForType.searchingForTypeFile == searchingFor)
                || (DiskScan.searchingForType.searchingForTypeBoth == searchingFor))
                    directoriesOnly = false;
                showingHiddenFolders = configInfo.ShowHiddenFoldersFiles;   //create local copy
                if (!String.IsNullOrEmpty(filesToFind))
                {   //build regular expressions for all file masks
                    fileMasks = filesToFind.Split(new char[1] { Path.PathSeparator });
                    wildCards = new List<Wildcard>(fileMasks.Length);
                    foreach (string mask in fileMasks)  //set up masks as regular expressions
                        wildCards.Add(new Wildcard(mask,
                                System.Text.RegularExpressions.RegexOptions.IgnoreCase));
                    filterRules = ConfigInfo.GetFilterRules;    //get a local reference to the current filter set rules
                    ArrayList disksToScan = GetScanAreas();
                    foreach (string disk in disksToScan)
                        try
                        {
                            DriveInfo driveInfo = new DriveInfo(disk);
                            if (driveInfo.IsReady)
                            {
                                scanningSpecificPaths = false;
                                if (disk.Length > 3)
                                    scanningSpecificPaths = true;
                                volumeId = driveInfo.VolumeLabel;
                                if (configInfo.ScanningViaEnumerator
                                || (DriveType.Network == driveInfo.DriveType))
                                    scanViaEnumerator = true;
                                else
                                    scanViaEnumerator = false;
#if useRecurseFoldersOnly
                                if (DiskScan.searchingForType.searchingForTypeFolder == searchingFor)
                                    RecurseFoldersOnly(disk);
                                else
#endif
                                    RecurseFileDir(disk);
                            }
                        }
                        catch (ThreadAbortException)
                        {   //stop the abort and fall through to the thread termination notification
                            Thread.ResetAbort();
                        }
                        catch (ObjectDisposedException)
                        { return; }
                        catch (UnauthorizedAccessException)
                        {
                            if (configInfo.ShowUnauthorizedFolders)
                            { SendMsg(" Unauthorized Access: " + disk); }
                            continue;
                        }
                        catch (InvalidOperationException ex)
                        {
                            MessageBox.Show("InvalidOperationException " + Thread.CurrentThread.Name
                                  + "\n" + ex.Message
                                  + "\n" + ex.Source,
                                  Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                            break;
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show("Error scanning " + Thread.CurrentThread.Name
                                //+ "\nType0: " + ex.GetType().ToString()
                                + "\n" + ex.Message
                                + "\n" + ex.Source,
                                Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                            continue;
                        }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error in DiskScan Run " + Thread.CurrentThread.Name
                    //+ "\nType0: " + ex.GetType().ToString()
                             + "\n" + ex.Message
                             + "\n" + ex.Source,
                             Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
            try { m_form.Invoke(ThreadTerminating, new Object[] { Thread.CurrentThread.Name }); }
            catch {}
        } //ends public void Run()

        /// <summary>
        /// Determine if the entire disk or only specific paths are to be searched.
        /// </summary>
        /// <returns>drive or path names to be searched</returns>
        private ArrayList GetScanAreas()
        {//search all drives assigned to this thread
            ArrayList scan = new ArrayList();
            string[] diskToScan = disksToScan.Split(new char[1] { ';' });
            foreach (string drive in diskToScan)
            {
                ArrayList scanPaths = filterRules.FetchPaths(drive, FilterRules.Functions.Include);
                if (0 == scanPaths.Count)
                    scan.Add(drive);            //search entire drive
                else
                    scan.AddRange(scanPaths);   //search only selected paths
            }
            return scan;
        } //ends private ArrayList GetScanAreas()

        void SendMsg(string msg)
        { m_form.Invoke(DisplayMsg, new Object[] { msg }); }

        void SendingDirInfo(DirectoryInfo directoryInfo)
        {
            int pause = (int)m_form.Invoke(SendDirInfo, directoryInfo, volumeId); 
            if (0 != pause)
                Thread.Sleep(pause);
        }

        void SendingFileInfo(FileInfo fileInfo)
        {
            int pause = (int)m_form.Invoke(SendFileInfo, fileInfo, volumeId);
            if (0 != pause)
                Thread.Sleep(pause);
        }

        /// <summary>
        /// Recurse through all folders on a disk to insure pattern matches 
        /// for folders and files are found.
        /// Any changes made to this module must also be made to RecurseEnumerator(...)
        /// </summary>
        /// <param name="msPathName">
        /// The first entry to this routine is the top level disk name, ie C:\ D:\ etc.
        /// There after, it contains the recursive folder names on the disk.
        /// </param>
        private void RecurseFileDir(string msPathName)
        {   //called recursively to search for path and file names
            if (pleaseTerminate.WaitOne(0, true))
                return;
            int pleaseTerminateCounter = 0; //number of files to process before checking pleaseTerminate
            DirectoryInfo processDirectory = new DirectoryInfo(msPathName);
            if ((!processDirectory.Exists)
            || ((processDirectory.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint))
                return;
            if ((processDirectory.Attributes & FileAttributes.System) != FileAttributes.System)
                if (((processDirectory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                && !showingHiddenFolders)
                    return;
            if (scanViaEnumerator)
            {
                using (FileSystemEnumerator fse = new FileSystemEnumerator(msPathName, directoriesOnly))
                {
                    IEnumerator<FileSystemInfo> ien = fse.Matches().GetEnumerator();
                    foreach (FileSystemInfo fileSystemInfo in fse.Matches())
                    {
                        if (++pleaseTerminateCounter > 5)
                        {   //check only occasionally to avoid overhead
                            pleaseTerminateCounter = 0;
                            if (pleaseTerminate.WaitOne(0, true))
                                return;
                        }
                        ProcessFileSystemInfo(fileSystemInfo, ref msPathName);
                    }
                }
            }
            else
            {
                FileSystemInfo[] fileSystemInfos = processDirectory.GetFileSystemInfos();
                foreach (FileSystemInfo fileSystemInfo in fileSystemInfos)
                {
                    if (++pleaseTerminateCounter > 5)
                    {   //check only occasionally to avoid overhead
                        pleaseTerminateCounter = 0;
                        if (pleaseTerminate.WaitOne(0, true))
                            return;
                    }
                    ProcessFileSystemInfo(fileSystemInfo, ref msPathName);
                }
            }
        } //ends private void RecurseFileDir(...

#if useRecurseFoldersOnly
        /// <summary>
        /// Recurse through all folders on a disk to insure pattern matches for all folders are found.
        /// </summary>
        /// <param name="msPathName">
        /// The first entry to this routine is the top level disk name, ie C:\ D:\ etc.
        /// There after, it contains the recursive folder names on the disk.
        /// </param>
        private void RecurseFoldersOnly(string msPathName)
        {   //called recursively to search for path and file names
            if (pleaseTerminate.WaitOne(0, true))
                return;
            int pleaseTerminateCounter = 0;
            DirectoryInfo processDirectory = new DirectoryInfo(msPathName);
            if ((!processDirectory.Exists)
            || ((processDirectory.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint))
                return;
            if ((processDirectory.Attributes & FileAttributes.System) != FileAttributes.System)
                if (!showingHiddenFolders
                && ((processDirectory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden))
                    return;

            DirectoryInfo[] directoryInfoInfos = processDirectory.GetDirectories("*");
            foreach (DirectoryInfo directoryInfo in directoryInfoInfos)
            {
                try
                {
                    if (++pleaseTerminateCounter > 5)
                    {
                        pleaseTerminateCounter = 0;
                        if (pleaseTerminate.WaitOne(0, true))
                            return;
                    }
                    if (!filterRules.SearchThisPath(directoryInfo))
                        continue;   //do not process
                    try
                    {
                        msPathName = directoryInfo.FullName;
                        bool wildCardCheck = false;
                        switch (usingDateTimeRange.IDateTimeRange)
                        {
                            case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeOff:
                                wildCardCheck = true;
                                break;
                            case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByCreate:
                                {
                                    DateTime dateTime = directoryInfo.CreationTime;
                                    if ((dateTime >= fromFileTime)
                                    && (dateTime <= toFileTime))
                                        wildCardCheck = true;
                                }
                                break;
                            case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByModified:
                                {
                                    DateTime dateTime = directoryInfo.LastWriteTime;
                                    if ((dateTime >= fromFileTime)
                                    && (dateTime <= toFileTime))
                                        wildCardCheck = true;
                                }
                                break;
                            case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByLastAccessed:
                                {
                                    DateTime dateTime = directoryInfo.LastAccessTime;
                                    if ((dateTime >= fromFileTime)
                                    && (dateTime <= toFileTime))
                                        wildCardCheck = true;
                                }
                                break;
                        }
                        if (wildCardCheck)
                            foreach (Wildcard wildCard in wildCards)
                            {
                                if (wildCard.IsMatch(directoryInfo.Name))
                                {
                                    SendingDirInfo(directoryInfo);
                                    break;      //do not check any further wildcard masks
                                }
                            } //ends foreach (Wildcard wildCard...
                    }
                    catch (PathTooLongException)
                    { SendMsg(" Path/File too long; ignoring " + msPathName); continue; }
                    catch (UnauthorizedAccessException ex)
                    {
                        if (configInfo.ShowUnauthorizedFolders)
                        { SendMsg(ex.Message); continue; }
                    }
                }
                catch (UnauthorizedAccessException)
                {
                    if (configInfo.ShowUnauthorizedFolders)
                    { SendMsg(" Unauthorized Access: " + msPathName); }
                    continue;
                }
                catch (ThreadAbortException)
                { return; }
                catch (ObjectDisposedException)
                { throw; }
                catch (IOException)
                { throw; }
                catch (InvalidOperationException)
                { throw; }
                catch (Exception ex)
                {
                    MessageBox.Show("Error scanning " + Thread.CurrentThread.Name
                        //+ "\nType3: " + ex.GetType().ToString()
                        + "\n" + ex.Message
                        + "\n" + ex.Source,
                        Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    continue;
                }
                if (Directory.Exists(msPathName))
                {
                    try { RecurseFoldersOnly(msPathName); }
                    catch (UnauthorizedAccessException)
                    {
                        if (configInfo.ShowUnauthorizedFolders)
                        { SendMsg(" Unauthorized Access: " + msPathName); }
                        continue;
                    }
                }
            }
        } //ends private void RecurseFoldersOnly(...
#endif
        private void ProcessFileSystemInfo(FileSystemInfo fileSystemInfo, ref string msPathName)
        {
            try
            {
                if (fileSystemInfo is DirectoryInfo)
                {   //directories are always searched
                    DirectoryInfo directoryInfo = fileSystemInfo as DirectoryInfo;
                    if (scanningSpecificPaths)
                    {
                        if (filterRules.SpecificallyExcludedPath(directoryInfo))
                            return;
                    }
                    else if (!filterRules.SearchThisPath(directoryInfo))
                        return;   //do not process
                    msPathName = directoryInfo.FullName;
                    if ((DiskScan.searchingForType.searchingForTypeFolder == searchingFor)
                    || (DiskScan.searchingForType.searchingForTypeBoth == searchingFor))
                        try
                        {
                            bool wildCardCheck = false;
                            switch (usingDateTimeRange.IDateTimeRange)
                            {
                                case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeOff:
                                    wildCardCheck = true;
                                    break;
                                case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByCreate:
                                    {
                                        DateTime dateTime = directoryInfo.CreationTime;
                                        if ((dateTime >= fromFileTime)
                                        && (dateTime <= toFileTime))
                                            wildCardCheck = true;
                                    }
                                    break;
                                case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByModified:
                                    {
                                        DateTime dateTime = directoryInfo.LastWriteTime;
                                        if ((dateTime >= fromFileTime)
                                        && (dateTime <= toFileTime))
                                            wildCardCheck = true;
                                    }
                                    break;
                                case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByLastAccessed:
                                    {
                                        DateTime dateTime = directoryInfo.LastAccessTime;
                                        if ((dateTime >= fromFileTime)
                                        && (dateTime <= toFileTime))
                                            wildCardCheck = true;
                                    }
                                    break;
                            }
                            if (wildCardCheck)
                                foreach (Wildcard wildCard in wildCards)
                                {
                                    if (wildCard.IsMatch(directoryInfo.Name))
                                    {
                                        SendingDirInfo(directoryInfo);
                                        break;      //do not check any further wildcard masks
                                    }
                                } //ends foreach (Wildcard wildCard...
                        }
                        catch (PathTooLongException)
                        {
                            SendMsg(" Path/File too long; ignoring " + msPathName);
                            return;
                        }
                        catch (UnauthorizedAccessException ex)
                        {
                            if (configInfo.ShowUnauthorizedFolders)
                            {
                                SendMsg(ex.Message);
                                return;
                            }
                        }
                        catch (InvalidOperationException)
                        { throw; }
                    if (Directory.Exists(msPathName))
                    {
                        try { RecurseFileDir(msPathName); }
                        catch (UnauthorizedAccessException)
                        {
                            if (configInfo.ShowUnauthorizedFolders)
                            { SendMsg(" Unauthorized Access: " + msPathName); }
                            return;
                        }
                    }
                } //ends if (fileSystemInfo is DirectoryInfo)
                else if ((fileSystemInfo is FileInfo)
                     && ((DiskScan.searchingForType.searchingForTypeFile == searchingFor)
                        || (DiskScan.searchingForType.searchingForTypeBoth == searchingFor)))
                {
                    FileInfo fileInfo = fileSystemInfo as FileInfo;
                    if (((fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                    && !showingHiddenFolders)
                        return;
                    switch (usingDateTimeRange.IDateTimeRange)
                    {
                        case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeOff:
                            break;
                        case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByCreate:
                            if ((fileInfo.CreationTime >= fromFileTime)
                            && (fileInfo.CreationTime <= toFileTime))
                                break;
                            else
                                return;
                        case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByModified:
                            if ((fileInfo.LastWriteTime >= fromFileTime)
                            && (fileInfo.LastWriteTime <= toFileTime))
                                break;
                            else
                                return;
                        case DateTimeInfo.DateTimeRangeSearch.DateTimeRangeByLastAccessed:
                            if ((fileInfo.LastAccessTime >= fromFileTime)
                            && (fileInfo.LastAccessTime <= toFileTime))
                                break;
                            else
                                return;
                    }
                    foreach (Wildcard wildCard in wildCards)
                    {
                        try
                        {
                            if (wildCard.IsMatch(fileInfo.Name))
                                try
                                {
                                    if (fileInfo.FullName != "." && fileInfo.FullName != "..")
                                    {
                                        SendingFileInfo(fileInfo);
                                        break;  //do not check any further wildcard masks
                                    }
                                }
                                catch (UnauthorizedAccessException)
                                {
                                    if (configInfo.ShowUnauthorizedFolders)
                                    { SendMsg(" Unauthorized Access: " + msPathName); }
                                    continue;
                                }
                        }
                        catch (PathTooLongException)
                        { SendMsg(" File/Path too long; ignoring " + msPathName); continue; }
                    } //ends foreach(Wildcard...
                } //ends if ((fileSystemInfo is FileInfo...
            }
            catch (UnauthorizedAccessException)
            {
                if (configInfo.ShowUnauthorizedFolders)
                { SendMsg(" Unauthorized Access: " + msPathName); }
                return;
            }
            catch (ThreadAbortException)
            { return; }
            catch (ObjectDisposedException)
            { throw; }
            catch (IOException)
            { throw; }
            catch (InvalidOperationException)
            { throw; }
            catch (Exception ex)
            {
                MessageBox.Show("Error scanning " + Thread.CurrentThread.Name
                    //+ "\nType1: " + ex.GetType().ToString()
                    + "\n" + ex.Message
                    + "\n" + ex.Source,
                    Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
        } //ends private void ProcessFileSystemInfo(FileSystemInfo fileSystemInfo)
    }
}

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 Code Project Open License (CPOL)


Written By
Software Developer (Senior) retired
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions