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
}