using System.Collections.Generic;
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Collections;
namespace Sleis.Utility
{
///
/// 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.
///
public class FileSystemEnumerator : DisposableBase, IEnumerable
{
[Flags]
public enum EReturnTypes
{
eReturnFiles = 0x0001,
eReturnDirectories = 0x0002,
eReturnAll = (eReturnFiles | eReturnDirectories)
}
///
/// Constructor.
///
/// Semicolon- or comma-delimitted list of paths to search.
/// Semicolon- or comma-delimitted list of wildcard filespecs to match.
/// If true, subdirectories are searched.
public FileSystemEnumerator(string pathsToSearch, string fileTypesToMatch, bool includeSubDirs,
EReturnTypes inReturnTypes)
{
_scopes = new Stack();
_returnFiles = (inReturnTypes & EReturnTypes.eReturnFiles) == EReturnTypes.eReturnFiles;
_returnDirectories = (inReturnTypes & EReturnTypes.eReturnDirectories) == EReturnTypes.eReturnDirectories;
// check for nulls
if (string.IsNullOrEmpty(pathsToSearch))
throw new ArgumentNullException("pathsToSearch");
if (string.IsNullOrEmpty(fileTypesToMatch))
throw new ArgumentNullException("fileTypesToMatch");
// make sure spec doesn't contain invalid characters
if (fileTypesToMatch.IndexOfAny(new char[] { ':', '<', '>', '/', '\\' }) >= 0)
throw new ArgumentException("invalid cahracters in wildcard pattern", "fileTypesToMatch");
_includeSubDirs = includeSubDirs;
_paths = pathsToSearch.Split(new char[] { ';', ',' });
string[] specs = fileTypesToMatch.Split(new char[] { ';', ',' });
_fileSpecs = new List(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("?", @".?")
;
_fileSpecs.Add(
new Regex("^" + pattern + "$", RegexOptions.IgnoreCase)
);
}
}
///
/// Information that's kept in our stack for simulated recursion
///
private struct SearchInfo
{
///
/// Find handle returned by FindFirstFile
///
public SafeFindHandle Handle;
///
/// Path that was searched to yield the find handle.
///
public string Path;
///
/// Constructor
///
/// Find handle returned by FindFirstFile.
/// Path corresponding to find handle.
public SearchInfo(SafeFindHandle h, string p)
{
Handle = h;
Path = p;
}
}
///
/// 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.
///
private Stack _scopes;
///
/// Array of paths to be searched.
///
private string[] _paths;
///
/// Array of regular expressions that will detect matching files.
///
private List _fileSpecs;
private bool _returnFiles;
private bool _returnDirectories;
///
/// If true, sub-directories are searched.
///
private bool _includeSubDirs;
#region IDisposable implementation
protected override void OnDispose(bool inIsDisposing)
{
if (inIsDisposing)
{
while (_scopes.Count > 0)
{
SearchInfo si = _scopes.Pop();
si.Handle.Close();
}
}
}
#endregion
///
/// Get an enumerator that returns all of the files that match the wildcards that
/// are in any of the directories to be searched.
///
/// An IEnumerable that returns all matching files one by one.
/// The enumerator that is returned finds files using a lazy algorithm that
/// searches directories incrementally as matches are consumed.
IEnumerator IEnumerable.GetEnumerator()
{
return Matches();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Matches();
}
private IEnumerator Matches()
{
Stack pathsToSearch = new Stack(_paths);
FindData findData = new FindData();
string path, fileName;
while (0 != pathsToSearch.Count)
{
path = pathsToSearch.Pop().Trim();
using (SafeFindHandle handle = FindFirstFile(
Path.Combine(path, "*"), findData))
{
if (!handle.IsInvalid)
{
do
{
fileName = findData.fileName;
if (string.IsNullOrEmpty(fileName)) continue;
if (string.Equals(fileName, ".", StringComparison.Ordinal)) continue;
if (string.Equals(fileName, "..", StringComparison.Ordinal)) continue;
if (0 != ((int)FileAttributes.Directory & findData.fileAttributes))
{
if (_returnDirectories)
{
foreach (Regex fileSpec in _fileSpecs)
{
if (fileSpec.IsMatch(fileName))
{
yield return Path.Combine(path, fileName);
break;
}
}
}
if (_includeSubDirs)
{
pathsToSearch.Push(Path.Combine(path, fileName));
}
}
else
{
if (_returnFiles)
{
foreach (Regex fileSpec in _fileSpecs)
{
if (fileSpec.IsMatch(fileName))
{
yield return Path.Combine(path, fileName);
break;
}
}
}
}
}
while (FindNextFile(handle, findData));
}
}
}
}
///
/// Structure that maps to WIN32_FIND_DATA
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible"), StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
private sealed class FindData
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int fileAttributes;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int creationTime_lowDateTime;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int creationTime_highDateTime;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int lastAccessTime_lowDateTime;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int lastAccessTime_highDateTime;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int lastWriteTime_lowDateTime;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int lastWriteTime_highDateTime;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int nFileSizeHigh;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int nFileSizeLow;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int dwReserved0;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
public int dwReserved1;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public String fileName;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public String alternateFileName;
}
///
/// SafeHandle class for holding find handles
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
private sealed class SafeFindHandle : Microsoft.Win32.SafeHandles.SafeHandleMinusOneIsInvalid
{
///
/// Constructor
///
public SafeFindHandle()
: base(true)
{
}
///
/// Release the find handle
///
/// true if the handle was released
protected override bool ReleaseHandle()
{
return FindClose(handle);
}
}
[DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
[SuppressMessage("Microsoft.Interoperability", "CA1401")]
private static extern SafeFindHandle FindFirstFile(String fileName, [In, Out] FindData findFileData);
[DllImport("kernel32", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
[SuppressMessage("Microsoft.Interoperability", "CA1401")]
private static extern bool FindNextFile(SafeFindHandle hFindFile, [In, Out] FindData lpFindFileData);
[DllImport("kernel32", CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
[SuppressMessage("Microsoft.Interoperability", "CA1401")]
private static extern bool FindClose(IntPtr hFindFile);
}
}