From 25bbea0973302a707543c16e20046dd240af7d10 Mon Sep 17 00:00:00 2001 From: Jay Asbury Date: Thu, 25 May 2017 14:10:04 -0400 Subject: [PATCH] Refactored FileSystemEnumerator Get rid of spaghetti code --- FeedBuilder/FileSystemEnumerator.cs | 217 +++++----------------------- 1 file changed, 39 insertions(+), 178 deletions(-) diff --git a/FeedBuilder/FileSystemEnumerator.cs b/FeedBuilder/FileSystemEnumerator.cs index fd7e3877..e47c3eae 100644 --- a/FeedBuilder/FileSystemEnumerator.cs +++ b/FeedBuilder/FileSystemEnumerator.cs @@ -1,121 +1,18 @@ 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; -using FeedBuilder.Win32; -using Microsoft.Win32.SafeHandles; namespace FeedBuilder { - namespace Win32 - { - /// - /// Structure that maps to WIN32_FIND_DATA - /// - [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; - } - - /// - /// SafeHandle class for holding find handles - /// - internal sealed class SafeFindHandle : SafeHandleMinusOneIsInvalid - { - /// - /// Constructor - /// - public SafeFindHandle() : base(true) { } - - /// - /// Release the find handle - /// - /// true if the handle was released - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - protected override bool ReleaseHandle() - { - return SafeNativeMethods.FindClose(handle); - } - } - - /// - /// Wrapper for P/Invoke methods used by FileSystemEnumerator - /// - [SecurityPermission(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); - } - } - /// /// 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 sealed class FileSystemEnumerator : IDisposable + public sealed class FileSystemEnumerator { - /// - /// Information that's kept in our stack for simulated recursion - /// - private struct SearchInfo - { - /// - /// Find handle returned by FindFirstFile - /// - public readonly SafeFindHandle Handle; - - /// - /// Path that was searched to yield the find handle. - /// - public readonly 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 readonly Stack m_scopes; - /// /// Array of paths to be searched. /// @@ -131,22 +28,6 @@ public SearchInfo(SafeFindHandle h, string p) /// private readonly bool m_includeSubDirs; - #region IDisposable implementation - - /// - /// IDisposable.Dispose - /// - public void Dispose() - { - while (m_scopes.Count > 0) - { - SearchInfo si = m_scopes.Pop(); - si.Handle.Close(); - } - } - - #endregion - /// /// Constructor. /// @@ -155,14 +36,12 @@ public void Dispose() /// If true, subdirectories are searched. public FileSystemEnumerator(string pathsToSearch, string fileTypesToMatch, bool includeSubDirs) { - m_scopes = new Stack(); - // 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[] { ':', '<', '>', '/', '\\' }) >= 0) throw new ArgumentException("invalid cahracters in wildcard pattern", "fileTypesToMatch"); + if (fileTypesToMatch.IndexOfAny(new[] { ':', '<', '>', '/', '\\' }) >= 0) throw new ArgumentException("Invalid characters in wildcard pattern", "fileTypesToMatch"); m_includeSubDirs = includeSubDirs; m_paths = pathsToSearch.Split(new[] { ';', ',' }); @@ -177,6 +56,36 @@ public FileSystemEnumerator(string pathsToSearch, string fileTypesToMatch, bool } } + private IEnumerable ProcessFiles(string folderPath) + { + foreach (var file in Directory.GetFiles(folderPath)) + { + string fileName = Path.GetFileName(file); + foreach (Regex fileSpec in m_fileSpecs) + { + // if this spec matches, return this file's info + if (fileSpec.IsMatch(fileName)) + { + yield return new FileInfo(file); + break; + } + } + } + } + + private IEnumerable ProcessSubdirectories(string folderPath) + { + // check security - ensure that caller has rights to read this directory + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, Path.Combine(folderPath, ".")).Demand(); + foreach (var d in Directory.GetDirectories(folderPath)) + { + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, Path.Combine(d, ".")).Demand(); + yield return d; + foreach (var sd in ProcessSubdirectories(d)) + yield return sd; + } + } + /// /// Get an enumerator that returns all of the files that match the wildcards that /// are in any of the directories to be searched. @@ -192,71 +101,23 @@ public IEnumerable Matches() { 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 - FindData findData = new FindData(); - SafeFindHandle handle = SafeNativeMethods.FindFirstFile(Path.Combine(path, "*"), findData); - m_scopes.Push(new SearchInfo(handle, path)); - bool restart = false; + foreach (var fi in ProcessFiles(path)) + yield return fi; - // we "return" from a sub-directory by jumping to this spot - restart: - // ReSharper disable InvertIf - if (!handle.IsInvalid) + if (m_includeSubDirs) { - // ReSharper restore InvertIf - 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 (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) + foreach (var d in ProcessSubdirectories(path)) { - SearchInfo si = m_scopes.Peek(); - handle = si.Handle; - path = si.Path; - restart = true; - goto restart; + foreach (var fi in ProcessFiles(d)) + yield return fi; } } } } } } +