|
/*=============================================================================
FileSystemEnumerator.cs: Lazy enumerator for finding files in subdirectories.
http://www.codeproject.com/cs/files/FileSystemEnumerator.asp
Copyright (c) 2006 Carl Daniel. http://www.boost.org/LICENSE_1_0.txt
http://msdn.microsoft.com/msdnmag/issues/05/12/NETMatters/default.aspx (similiar)
=============================================================================*/
// ---------------------------------------------------------------------------
// FileSystemEnumerator implementation
// ---------------------------------------------------------------------------
// OC changes marked as #OC#
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text.RegularExpressions;
namespace FindFiles
{
namespace Win32
{
/// <summary>
/// Structure that maps to WIN32_FIND_DATA
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal sealed class FindData
{
public int fileAttributes;
public int creationTime_lowDateTime;
public int creationTime_highDateTime;
public int lastAccessTime_lowDateTime;
public int lastAccessTime_highDateTime;
public int lastWriteTime_lowDateTime;
public int lastWriteTime_highDateTime;
public int nFileSizeHigh;
public int nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public String fileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public String alternateFileName;
}
/// <summary>
/// SafeHandle class for holding find handles
/// </summary>
internal sealed class SafeFindHandle : Microsoft.Win32.SafeHandles.SafeHandleMinusOneIsInvalid
{
/// <summary>
/// Constructor
/// </summary>
public SafeFindHandle()
: base(true)
{
}
/// <summary>
/// Release the find handle
/// </summary>
/// <returns>true if the handle was released</returns>
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
protected override bool ReleaseHandle()
{
return SafeNativeMethods.FindClose(handle);
}
}
/// <summary>
/// Wrapper for P/Invoke methods used by FileSystemEnumerator
/// </summary>
[SecurityPermissionAttribute(SecurityAction.Assert, UnmanagedCode = true)]
internal static class SafeNativeMethods
{
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
public static extern SafeFindHandle FindFirstFile(String fileName, [In, Out] FindData findFileData);
[DllImport("kernel32", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindNextFile(SafeFindHandle hFindFile, [In, Out] FindData lpFindFileData);
[DllImport("kernel32", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindClose(IntPtr hFindFile);
}
}
/// <summary>
/// File system enumerator. This class provides an easy to use, efficient mechanism for searching a list of
/// directories for files matching a list of file specifications. The search is done incrementally as matches
/// are consumed, so the overhead before processing the first match is always kept to a minimum.
/// </summary>
public sealed class FileSystemEnumerator : IDisposable
{
/// <summary>
/// Information that's kept in our stack for simulated recursion
/// </summary>
private struct SearchInfo
{
/// <summary>
/// Find handle returned by FindFirstFile
/// </summary>
public Win32.SafeFindHandle Handle;
/// <summary>
/// Path that was searched to yield the find handle.
/// </summary>
public string Path;
/// <summary>
/// Constructor
/// </summary>
/// <param name="h">Find handle returned by FindFirstFile.</param>
/// <param name="p">Path corresponding to find handle.</param>
public SearchInfo(Win32.SafeFindHandle h, string p)
{
Handle = h;
Path = p;
}
}
/// <summary>
/// Stack of open scopes. This is a member (instead of a local variable)
/// to allow Dispose to close any open find handles if the object is disposed
/// before the enumeration is completed.
/// </summary>
private Stack<SearchInfo> m_scopes;
/// <summary>
/// Array of paths to be searched.
/// </summary>
private string[] m_paths;
/// <summary>
/// Array of regular expressions that will detect matching files.
/// </summary>
private List<Regex> m_fileSpecs;
/// <summary>
/// If true, sub-directories are searched.
/// </summary>
private bool m_includeSubDirs;
#region IDisposable implementation
/// <summary>
/// IDisposable.Dispose
/// </summary>
public void Dispose()
{
while (m_scopes.Count > 0)
{
SearchInfo si = m_scopes.Pop();
si.Handle.Close();
}
}
#endregion
/// <summary>
/// Constructor.
/// </summary>
/// <param name="pathsToSearch">Delimitted list of paths to search, delimiters may be any invalid path chars.</param> // #OC#
/// <param name="fileTypesToMatch">Semicolon- or comma-delimited list of wildcard filespecs to match.</param>
/// <param name="includeSubDirs">If true, subdirectories are searched.</param>
public FileSystemEnumerator(string pathsToSearch, string fileTypesToMatch, bool includeSubDirs)
{
m_scopes = new Stack<SearchInfo>();
// check for nulls
if (null == pathsToSearch)
throw new ArgumentNullException("pathsToSearch");
if (null == fileTypesToMatch)
throw new ArgumentNullException("fileTypesToMatch");
// make sure spec doesn't contain invalid characters
if (fileTypesToMatch.IndexOfAny(new char[] { ':', '<', '>', '/', '\\' }) >= 0)
throw new ArgumentException("invalid characters in wildcard pattern", "fileTypesToMatch");
m_includeSubDirs = includeSubDirs;
//m_paths = pathsToSearch.Split(new char[] { ':', ',' }); // #OC#
m_paths = pathsToSearch.Split(Path.GetInvalidPathChars());
string[] specs = fileTypesToMatch.Split(new char[] { ';', ',' });
m_fileSpecs = new List<Regex>(specs.Length);
foreach (string spec in specs)
{
// trim whitespace off file spec and convert Win32 wildcards to regular expressions
string pattern = spec
.Trim()
.Replace(".", @"\.")
.Replace("*", @".*")
.Replace("?", @".?")
;
m_fileSpecs.Add(
new Regex("^" + pattern + "$", RegexOptions.IgnoreCase)
);
}
}
/// <summary>
/// Get an enumerator that returns all of the files that match the wildcards that
/// are in any of the directories to be searched.
/// </summary>
/// <returns>An IEnumerable that returns all matching files one by one.</returns>
/// <remarks>The enumerator that is returned finds files using a lazy algorithm that
/// searches directories incrementally as matches are consumed.</remarks>
public IEnumerable<FileInfo> Matches()
{
foreach (string rootPath in m_paths)
{
string path = rootPath.Trim();
// we "recurse" into a new directory by jumping to this spot
top:
// check security - ensure that caller has rights to read this directory
new FileIOPermission(FileIOPermissionAccess.PathDiscovery, Path.Combine(path, ".")).Demand();
// now that security is checked, go read the directory
Win32.FindData findData = new Win32.FindData();
Win32.SafeFindHandle handle = Win32.SafeNativeMethods.FindFirstFile(Path.Combine(path, "*"), findData);
m_scopes.Push(new SearchInfo(handle, path));
bool restart = false;
// we "return" from a sub-directory by jumping to this spot
restart:
if (!handle.IsInvalid)
{
do
{
// if we restarted the loop (unwound a recursion), fetch the next match
if (restart)
{
restart = false;
continue;
}
// don't match . or ..
if (findData.fileName.Equals(@".") || findData.fileName.Equals(@".."))
continue;
if ((findData.fileAttributes & (int)FileAttributes.Directory) != 0)
{
if (m_includeSubDirs)
{
// it's a directory - recurse into it
path = Path.Combine(path, findData.fileName);
goto top;
}
}
else
{
// it's a file, see if any of the filespecs matches it
foreach (Regex fileSpec in m_fileSpecs)
{
// if this spec matches, return this file's info
if (fileSpec.IsMatch(findData.fileName))
yield return new FileInfo(Path.Combine(path, findData.fileName));
}
}
} while (Win32.SafeNativeMethods.FindNextFile(handle, findData));
// close this find handle
handle.Close();
// unwind the stack - are we still in a recursion?
m_scopes.Pop();
if (m_scopes.Count > 0)
{
SearchInfo si = m_scopes.Peek();
handle = si.Handle;
path = si.Path;
restart = true;
goto restart;
}
}
}
}
}
}
|
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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.