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 Microsoft.Win32.SafeHandles;
using Wox.UpdateFeedGenerator.Win32;
namespace Wox.UpdateFeedGenerator
{
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
{
///
/// 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.
///
private readonly string[] m_paths;
///
/// Array of regular expressions that will detect matching files.
///
private readonly List m_fileSpecs;
///
/// If true, sub-directories are searched.
///
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.
///
/// 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)
{
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");
m_includeSubDirs = includeSubDirs;
m_paths = pathsToSearch.Split(';', ',');
string[] specs = fileTypesToMatch.Split(';', ',');
m_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("?", @".?");
m_fileSpecs.Add(new Regex("^" + pattern + "$", RegexOptions.IgnoreCase));
}
}
///
/// 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.
///
public IEnumerable 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
FindData findData = new FindData();
SafeFindHandle handle = 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:
// ReSharper disable InvertIf
if (!handle.IsInvalid) {
// 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) {
SearchInfo si = m_scopes.Peek();
handle = si.Handle;
path = si.Path;
restart = true;
goto restart;
}
}
}
}
}
}