using System; using System.IO; using System.ComponentModel; using System.Runtime.Serialization; using System.Runtime.InteropServices; using ComTypes = System.Runtime.InteropServices.ComTypes; namespace Orthogonal.Samples { #region FINDER_START #region FileFinderException Class /// /// The exception that is thrown when the detects /// an unexpected Win32 error code during the file find process. If the inner /// exception is a Win32Exception then it will contain details of the /// unmanaged error. /// public sealed class FileFinderException : Exception { public FileFinderException() { } public FileFinderException(string msg) : base(msg) { } public FileFinderException(string msg, Exception ex) : base(msg, ex) { } public FileFinderException(SerializationInfo info, StreamingContext context) : base(info, context) { } private int errorCode; public int ErrorCode { get { return errorCode; } set { errorCode = value; } } } #endregion #region Callback Handlers and Events /// /// This event is fired by the file finder when a file or folder is found. Either the /// File or Folder property of the event arguments will be not null, /// indicating what sort of file system object was found. /// public delegate void FileFoundEventHandler(object sender, FileFoundEventArgs e); [Serializable] public class FileFoundEventArgs : EventArgs { public FileFoundEventArgs() { } public FileFoundEventArgs(FileInfo file, DirectoryInfo folder) { this.file = file; this.folder = folder; } private FileInfo file; /// /// Gets or sets a reference to the FileInfo for a found file. This property is not null when /// a file is found during the search and null when a folder is found. /// public FileInfo File { get { return file; } set { file = value; } } private DirectoryInfo folder; /// /// Gets or sets a reference toa DirectoryInfo for a found folder. This property is not null /// when a folder is found during the search and null when a file is found. /// public DirectoryInfo Folder { get { return folder; } set { folder = value; } } private bool cancel = false; /// /// Calls can set this property to true to cancel search processing. /// public bool Cancel { get { return cancel; } set { cancel = value; } } } #endregion #region Win32FileDefs Class /// /// A static class of interop structure definitions and constants, taken /// from WinDef.h, WinNT.h and WinBase.h in the C++ SDK. /// #if NET11 public class Win32FileDefs { private Win32FileDefs() { } #else public static class Win32FileDefs { #endif #region Structures and Constants public const int ERROR_FILE_NOT_FOUND = 2; public const int ERROR_NO_MORE_FILES = 18; public const int FILE_ATTRIBUTE_READONLY = 0x00000001; public const int FILE_ATTRIBUTE_HIDDEN = 0x00000002; public const int FILE_ATTRIBUTE_SYSTEM = 0x00000004; public const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; public const int FILE_ATTRIBUTE_ARCHIVE = 0x00000020; public const int FILE_ATTRIBUTE_DEVICE = 0x00000040; public const int FILE_ATTRIBUTE_NORMAL = 0x00000080; public const int FILE_ATTRIBUTE_TEMPORARY = 0x00000100; public const int FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200; public const int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; public const int FILE_ATTRIBUTE_COMPRESSED = 0x00000800; public const int FILE_ATTRIBUTE_OFFLINE = 0x00001000; public const int FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000; public const int FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; public const int MAX_PATH = 260; public static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct WIN32_FIND_DATA { public int dwFileAttributes; #if NET11 public long ftCreationTime; public long ftLastAccessTime; public long ftLastWriteTime; #else public ComTypes.FILETIME ftCreationTime; public ComTypes.FILETIME ftLastAccessTime; public ComTypes.FILETIME ftLastWriteTime; #endif public int nFileSizeHigh; public int nFileSizeLow; public int dwReserved0; public int dwReserved1; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] public string FileName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] public string AlternateFileName; } #endregion #region API Definitions [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int FindNextFile(IntPtr handle, out WIN32_FIND_DATA lpFindFileData); [DllImport("kernel32.dll", SetLastError = true)] public static extern int FindClose(IntPtr handle); #endregion } #endregion #region Win32FileFinder Class /// /// A utility class that wraps the Win32 file finding functions in a familar /// and convenient managed way with callbacks while hiding the underlying /// complexities of the API calls, handles and return codes. /// /// /// The .NET Framework methods in System.IO for finding files and folders perform /// a bulk operation and return arrays of FileInfo or DirectoryInfo objects. /// Callers can block for an unacceptably long time if large numbers of results /// are accumulated by methods like or . /// These methods are not interruptible and the results are not available until /// the method completes. /// This class uses the FindFirstFile, FindNextFile and /// FindClose Win32 API methods internally to run a find, sending an /// event with managed FileInfo or DirectoryInfo objects as callbacks /// so that consumers can work with the results as they are found. The caller /// sequentally consumes the results and can cancel the find during a callback, /// which is not possible with the managed methods. /// The class does not yet recursively scan folders. This can be done, but /// it will be necessary to maintain a stack of the find handles. /// Created by Greg Keogh for the interest and education of other software /// developers. You are free to use the class in your own projects, but please /// let me know if you change the code to fix any bugs or add new features. My /// email address is greg@mira.net and my web page related to computers is /// http://www.orthogonal.com.au/computers/ - Greg (27-Oct-2007) /// public sealed class Win32FileFinder : IDisposable { public event FileFoundEventHandler FileFound; private Win32FileDefs.WIN32_FIND_DATA data; private IntPtr handle; private int lastError; private int rc; private string[] Ignores = { ".", ".." }; private string findRoot; public Win32FileFinder() { data = new Win32FileDefs.WIN32_FIND_DATA(); } /// /// Starts a file search using a specified file mask. Callers must listen to /// the event to process find results. /// /// A Win32 API HANDLE underlies the find process. /// A DOS style file mask of files to find. This argument /// does not behave exactly the same as the DOS dir command, as it is necessary /// to specify a '*' as the mask to find all files, unlike the dir command where /// the default is all files. /// Thrown if the find processing /// encounters an unexpected Win32 API error code. /// Thrown if the filemask argument /// is not in an acceptable form. public void Find(string filemask) { if (filemask == null) { filemask = "*"; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Before the search starts we have to calculate what the root // path is that's being searched. This will be combined with // the raw found file to create the full name for return. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ string s = Path.GetDirectoryName(filemask); if ((s == null) || (s.Length == 0)) { findRoot = Environment.CurrentDirectory; } else { if (Path.IsPathRooted(s)) { findRoot = s; } else { findRoot = new DirectoryInfo(s).FullName; } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Prime the find with a FindFirst call. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ handle = Win32FileDefs.FindFirstFile(filemask, out data); if (handle == Win32FileDefs.INVALID_HANDLE_VALUE) { lastError = Marshal.GetLastWin32Error(); if (lastError == Win32FileDefs.ERROR_FILE_NOT_FOUND) { return; } else { throw MakeException(lastError, "Unexpected FindFirstFile error"); } } else { if (!SendCallback(data)) { SafeCloseFinder(); return; } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Continue the find with FindNext calls. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ for (; ; ) { rc = Win32FileDefs.FindNextFile(handle, out data); if (rc == 0) { lastError = Marshal.GetLastWin32Error(); if (lastError == Win32FileDefs.ERROR_NO_MORE_FILES) { SafeCloseFinder(); break; } else { SafeCloseFinder(); throw MakeException(lastError, "Unexpected FindNextFile error"); } } else { if (!SendCallback(data)) { SafeCloseFinder(); break; } } } } #region Private Methods /// /// Safely closes the find handle, if it is not already closed. /// private void SafeCloseFinder() { if (handle != IntPtr.Zero) { if (handle != Win32FileDefs.INVALID_HANDLE_VALUE) { rc = Win32FileDefs.FindClose(handle); if (rc == 0) { lastError = Marshal.GetLastWin32Error(); throw MakeException(lastError, "Unexpected FindClose error"); } } handle = IntPtr.Zero; } } /// /// Helper method to make a FileFinderException for throwning when an /// unexpected error occurs. The inner exception contains information about /// the native error (the Win32Exception class constructor does all the hard /// work). /// private Exception MakeException(int errorCode, string format, params object[] args) { Win32Exception ex32 = new Win32Exception(errorCode); FileFinderException ex = new FileFinderException(string.Format(format, args), ex32); ex.ErrorCode = errorCode; return ex; } /// /// Sends a result event back to callers. The result is internally calculated /// to be a File or Folder based upon the bit patterns in the attributes returned /// from the API call. We currently make a simple distinction between DIRECTORY /// and all others, which are assumed to be files for the moment. Note that the /// dot paths are silently ignored as managed users aren't interested in them. /// private bool SendCallback(Win32FileDefs.WIN32_FIND_DATA data) { if (Array.IndexOf(Ignores, data.FileName) >= 0) { return true; } FileFoundEventArgs e = new FileFoundEventArgs(); string bestName = ((data.FileName == null) || (data.FileName.Length == 0) ? data.AlternateFileName : data.FileName); string fullName = Path.Combine(findRoot, bestName); if ((data.dwFileAttributes & Win32FileDefs.FILE_ATTRIBUTE_DIRECTORY) != 0) { e.Folder = new DirectoryInfo(fullName); FileFound(this, e); } else { e.File = new FileInfo(fullName); FileFound(this, e); } return !e.Cancel; } #endregion #region Disposing public void Dispose() { InnerDispose(true); GC.SuppressFinalize(this); } ~Win32FileFinder() { InnerDispose(false); } private void InnerDispose(bool disposing) { SafeCloseFinder(); } #endregion } #endregion #endregion FINDER_END }