Click here to Skip to main content
15,879,613 members
Articles / Programming Languages / C#

Scan directories using recursion

Rate me:
Please Sign up or sign in to vote.
4.69/5 (25 votes)
15 Jul 2008CPOL6 min read 132.2K   4.2K   95  
A generic class for scanning directories using recursion and events
using System;
using System.IO;

namespace ScanDirectoryDemo
{
	/// <summary>
	/// Defines the action on a directory which triggered the event
	/// </summary>
	public enum ScanDirectoryAction
	{
		/// <summary>
		/// Enter a directory
		/// </summary>
		Enter,

		/// <summary>
		/// Leave a directory
		/// </summary>
		Leave
	}

	#region Event argument definition for ScanDirectory.FileEvent

	/// <summary>
	/// Information about the file in the current directory.
	/// </summary>
	public class FileEventArgs : EventArgs
	{
		#region Constructors

		/// <summary>
		/// Block the default constructor.
		/// </summary>
		private FileEventArgs() {	}

		/// <summary>
		/// Initializes a new instance of the <see cref="DirectoryEventArgs"/> class.
		/// </summary>
		/// <param name="fileInfo"><see cref="FileInfo"/> object for the current file.</param>
		internal FileEventArgs(FileInfo fileInfo)
		{
			if (fileInfo == null) throw new ArgumentNullException("fileInfo");
			
			// Get File information 
			_fileInfo = fileInfo;
		}

		#endregion

		#region Properties

		private bool			_cancel;
		private FileInfo		_fileInfo;

		/// <summary>
		/// Gets the current file information.
		/// </summary>
		/// <value>The <see cref="FileInfo"/> object for the current file.</value>
		public FileInfo Info
		{
			get { return _fileInfo; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether to cancel the directory scan.
		/// </summary>
		/// <value>
		/// <see langword="true"/> if the scan must be cancelled; otherwise, <see langword="false"/>.
		/// </value>
		public bool Cancel
		{
			get { return _cancel; }
			set { _cancel = value; }
		}

		#endregion
	}

	#endregion

	#region Event argument definition for ScanDirectory.DirectoryEvent

	/// <summary>
	/// Event arguments for the DirectoryEvent
	/// </summary>
	public class DirectoryEventArgs : EventArgs
	{
		#region Constructors

		/// <summary>
		/// Block the default constructor.
		/// </summary>
		private DirectoryEventArgs() {	}

		/// <summary>
		/// Initializes a new instance of the <see cref="DirectoryEventArgs"/> class.
		/// </summary>
		/// <param name="directory"><see cref="DirectoryInfo"/> object for the current path.</param>
		/// <param name="action">The action.</param>
		internal DirectoryEventArgs(DirectoryInfo directory, ScanDirectoryAction action)
		{
			if (directory == null) throw new ArgumentNullException("directory");
			
			// Get File information 
			_directoryInfo = directory;
			_action = action;
		}

		#endregion

		#region Properties

		private DirectoryInfo		_directoryInfo;
		private ScanDirectoryAction	_action;
		private bool				_cancel;

		/// <summary>
		/// Gets the current directory information.
		/// </summary>
		/// <value>The <see cref="DirectoryInfo"/> object for the current directory.</value>
		public DirectoryInfo Info
		{
			get { return _directoryInfo; }
		}

		/// <summary>
		/// Gets the current directory action.
		/// </summary>
		/// <value>The <see cref="ScanDirectoryAction"/> action value.</value>
		public ScanDirectoryAction Action
		{
			get { return _action; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether to cancel the directory scan.
		/// </summary>
		/// <value>
		/// <see langword="true"/> if the scan must be cancelled; otherwise, <see langword="false"/>.
		/// </value>
		public bool Cancel
		{
			get { return _cancel; }
			set { _cancel = value; }
		}

		#endregion
	}

	#endregion

	/// <summary>
	/// Scan directory trees
	/// </summary>
	public class ScanDirectory
	{
		private const string _patternAllFiles = "*.*";

		#region Handling of the FileEvent

		/// <summary>
		/// Definition for the FileEvent.
		///	</summary>
		public delegate void FileEventHandler(object sender, FileEventArgs e); 

		/// <summary>
		/// Event is raised for each file in a directory.
		/// </summary>
		public event FileEventHandler FileEvent;

		/// <summary>
		/// Raises the file event.
		/// </summary>
		/// <param name="fileInfo"><see cref="FileInfo"/> object for the current file.</param>
		private bool RaiseFileEvent(FileInfo fileInfo)
		{
			bool continueScan = true;

			// Create a new argument object for the file event.
			FileEventArgs args = new FileEventArgs(fileInfo);

			// Now raise the event.
			FileEvent(this, args);

			continueScan = !args.Cancel;

			return continueScan;
		}

		#endregion

		#region Handling of the DirectoryEvent

		/// <summary>
		/// Definition for the DirectoryEvent.
		/// </summary>
		public delegate void DirectoryEventHandler(object sender, DirectoryEventArgs e); 

		/// <summary>
		/// Event is raised for each directory.
		/// </summary>
		public event DirectoryEventHandler DirectoryEvent;

		/// <summary>
		/// Raises the directory event.
		/// </summary>
		/// <param name="directory"><see cref="DirectoryInfo"/> object for the current path.</param>
		/// <param name="action">The <see cref="ScanDirectoryAction"/> action value.</param>
		/// <returns><see langword="true"/> when the scan is allowed to continue. <see langword="false"/> if otherwise;</returns>
		private bool RaiseDirectoryEvent(DirectoryInfo directory, ScanDirectoryAction action)
		{
			bool continueScan = true;

			// Only do something when the event has been declared.
			if (FileEvent != null)
			{
				// Create a new argument object for the file event.
				DirectoryEventArgs args = new DirectoryEventArgs(directory, action);

				// Now raise the event.
				DirectoryEvent(this, args);

				continueScan = !args.Cancel;
			}
			return continueScan;
		}

		#endregion

		#region Public methods

		/// <summary>
		/// Walks the specified path.
		/// </summary>
		/// <param name="path">The path.</param>
		/// <returns><see langword="true"/> when the scan finished without being interupted. <see langword="false"/> if otherwise;</returns>
		public bool WalkDirectory(string path)
		{
			// Validate path argument.
			if (path == null || path.Length == 0) throw new ArgumentNullException("path");

			return this.WalkDirectory(new DirectoryInfo(path));
		}

		/// <summary>
		/// Walks the specified directory.
		/// </summary>
		/// <param name="directory"><see cref="DirectoryInfo"/> object for the current path.</param>
		/// <returns><see langword="true"/> when the scan finished without being interupted. <see langword="false"/> if otherwise;</returns>
		public bool WalkDirectory(DirectoryInfo directory)
		{
			if (directory == null) 
			{
				throw new ArgumentNullException("directory");
			}

			return this.WalkDirectories(directory);
		}

		#endregion

		#region Overridable methods 

		/// <summary>
		/// Processes the directory.
		/// </summary>
		/// <param name="directoryInfo">The directory info.</param>
		/// <param name="action">The action.</param>
		/// <returns><see langword="true"/> when the scan is allowed to continue. <see langword="false"/> if otherwise;</returns>
		public virtual bool ProcessDirectory(DirectoryInfo directoryInfo, ScanDirectoryAction action)
		{
			if (DirectoryEvent != null)
			{
				return RaiseDirectoryEvent(directoryInfo, action);
			}	
			return true;
		}

		/// <summary>
		/// Processes the file.
		/// </summary>
		/// <param name="fileInfo">The file info.</param>
		/// <returns><see langword="true"/> when the scan is allowed to continue. <see langword="false"/> if otherwise;</returns>
		public virtual bool ProcessFile(FileInfo fileInfo)
		{
			// Only do something when the event has been declared.
			if (FileEvent != null)
			{
				RaiseFileEvent(fileInfo);
			}
			return true;
		}

		#endregion

		#region Private methods

		/// <summary>
		/// Walks the directory tree starting at the specified directory.
		/// </summary>
		/// <param name="directory"><see cref="DirectoryInfo"/> object for the current directory.</param>
		/// <returns><see langword="true"/> when the scan is allowed to continue. <see langword="false"/> if otherwise;</returns>
		private bool WalkDirectories(DirectoryInfo directory)
		{
			bool continueScan = true;

			if (continueScan = this.ProcessDirectory(directory, ScanDirectoryAction.Enter))
			{
				// Only scan the files in this path when a file event was specified 
				if (this.FileEvent != null)
				{
					continueScan = WalkFilesInDirectory(directory);
				}

				if (continueScan)
				{
					DirectoryInfo [] subDirectories = directory.GetDirectories();

					foreach (DirectoryInfo subDirectory in subDirectories)
					{
						// It is possible that users create a recursive directory by mounting a drive
						// into an existing directory on that same drive. If so, the attributes
						// will have the ReparsePoint flag active. The directory is then skipped.
						// See: http://blogs.msdn.com/oldnewthing/archive/2004/12/27/332704.aspx
						if ((subDirectory.Attributes & FileAttributes.ReparsePoint) != 0)
						{
							continue;
						}

						if (!(continueScan = this.WalkDirectory(subDirectory)))
						{
							break;
						}
					}
				}

				if (continueScan)
				{
					continueScan = this.ProcessDirectory(directory, ScanDirectoryAction.Leave);
				}
			}
			return continueScan;
		}

		/// <summary>
		/// Walks the directory tree starting at the specified path.
		/// </summary>
		/// <param name="directory"><see cref="DirectoryInfo"/> object for the current path.</param>
		/// <returns><see langword="true"/> when the scan was cancelled. <see langword="false"/> if otherwise;</returns>
		private bool WalkFilesInDirectory(DirectoryInfo directory)
		{
			bool continueScan = true;

			// Break up the search pattern in separate patterns
			string [] searchPatterns = _searchPattern.Split(';');

			// Try to find files for each search pattern
			foreach (string searchPattern in searchPatterns)
			{
				if (!continueScan)
				{
					break;
				}
				// Scan all files in the current path
				foreach (FileInfo file in directory.GetFiles(searchPattern))
				{
					if (!(continueScan = this.ProcessFile(file))) 
					{
						break;
					}
				}
			}
			return continueScan;
		}

		#endregion

		#region Properties

		private string _searchPattern;

		/// <summary>
		/// Gets or sets the search pattern.
		/// </summary>
		/// <example>
		/// You can specify more than one seach pattern
		/// </example>
		/// <value>The search pattern.</value>
		public string SearchPattern
		{
			get { return _searchPattern;  }
			set 
			{
				// When an empty value is specified, the search pattern will be the default (= *.*)
				if (value == null || value.Trim().Length == 0)
				{
					_searchPattern = _patternAllFiles;
				}
				else
				{
					_searchPattern = value; 
					// make sure the pattern does not end with a semi-colon
					_searchPattern = _searchPattern.TrimEnd(new char [] {';'});
				}
			}
		}

		#endregion
	}
}

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
Technical Lead
Netherlands Netherlands
I'm a professional software developer for a small company in the Netherlands. I have been developing software since 1988 in C, Visual Basic and C#. My main focus is now on problem analysis in IT projects.

Comments and Discussions