Thursday, November 7, 2013

CLR Function: Directory Listing

If you guys are anything like me, you will use this little function over and over again. I constantly have the need to list the files in a directory. Most of the time it is for backup file handling but I also use this function to work with log files, data exports, and reports. Before I wrote this function I used the good old xp_CmdShell sproc with “dir /b” into a temp table and then sorted it all out. Now I call the function and it returns a table with all of the attributes for each file as nice clean separated columns.
Now I can execute a simple command like:
SELECT      * 
FROM        dbaudf_DirectoryList2('C:\Program Files\Microsoft SQL Server\','*.SQL',1)

and I get a nice clean table that I can order or filter by any of the individual columns.




This was one of my first versions of the function and was fairly basic. This version only accepts a path and a filter and returns the data for the matching files in the specified directory.


using System;
using System.IO;
using System.Collections;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
 
/// <summary>
/// 
/// </summary>
public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction(
       DataAccess = DataAccessKind.None,
       FillRowMethodName = "FillRowDirectoryList",
       IsDeterministic = true,
       Name = "dbaudf_DirectoryList",
       TableDefinition = @" Name            nvarchar(4000)
                            , FullPathName  nvarchar(4000)
                            , IsFolder      bit
                            , Extension     nvarchar(4000)
                            , DateCreated   datetime
                            , DateAccessed  datetime
                            , DateModified  datetime
                            , Attributes    nVarChar(4000)
                            , Size          bigint
                            
                        ")]
 
    public static IEnumerable dbaudf_DirectoryList(SqlString path, SqlString filter)
    {
        Int16 errSeverity = 16;
        if (Directory.Exists(path.Value))
        {
        try
        {
            DirectoryInfo di = new DirectoryInfo(path.Value);
            if (filter.IsNull || filter.Value == string.Empty)
                return di.GetFileSystemInfos();
            else
                return di.GetFileSystemInfos(filter.Value);
        }
        catch (Exception ex)
        {
            // Connect to server in event of error
            SqlConnection conn = new SqlConnection("context connection=true");
            conn.Open();
 
            // Generate error response to SQL Server
            string msg = string.Format("RAISERROR('{0}',{1},1)", ex.Message.Replace('\'', ' '), errSeverity);
            SqlCommand cmd = new SqlCommand(msg, conn);
 
            try
            {
                // Execute error on SQL Server
                SqlPipe sqlpipe = SqlContext.Pipe;
                sqlpipe.ExecuteAndSend(cmd);
                Exception newEx = new Exception(msg);
                throw newEx;
            }
            catch { return null; } // Prevent error duplication
        }
        }
        else 
        {
            return null;
        };
    }
 
    private static void FillRowDirectoryList(object obj,
        out SqlString       altname,
        out SqlString       FullPathname,
        out SqlBoolean      directoryflag,
        out SqlString       extension,
        out SqlDateTime     datetimecreate,
        out SqlDateTime     datetimeaccessed,
        out SqlDateTime     datetimemodified,
        out SqlString       Attributes,
        out SqlInt64        size)
    {
        try
        {
            if (obj is FileInfo)
            {
                FileInfo fsi        = (FileInfo)obj;
                altname             = fsi.Name;
                FullPathname        = fsi.FullName;
                directoryflag       = SqlBoolean.False;
                extension           = fsi.Extension.ToString();
                datetimecreate      = fsi.CreationTime;
                datetimeaccessed    = fsi.LastAccessTime;
                datetimemodified    = fsi.LastWriteTime;
                Attributes          = fsi.Attributes.ToString();
                size                = fsi.Length;
            }
            else
            {
                FileSystemInfo fsi  = (FileSystemInfo)obj;
                altname             = fsi.Name;
                FullPathname        = fsi.FullName;
                directoryflag       = SqlBoolean.True;
                extension           = fsi.Extension.ToString();
                datetimecreate      = fsi.CreationTime;
                datetimeaccessed    = fsi.LastAccessTime;
                datetimemodified    = fsi.LastWriteTime;
                Attributes          = fsi.Attributes.ToString();
                size                = SqlInt64.Null;
            }
        }
        catch (Exception e)
        {
            throw e;
        }
    }
}




Then I decided to improve the process and give the function the ability to search subdirectories.


 
    [Microsoft.SqlServer.Server.SqlFunction(
       DataAccess = DataAccessKind.None,
       FillRowMethodName = "FillRowDir",
       IsDeterministic = true,
       Name = "dbaudf_DirectoryList2",
       TableDefinition = @" Name            nvarchar(4000)
                            , FullPathName  nvarchar(4000)
                            , Directory     nvarchar(4000)
                            , Extension     nvarchar(4000)
                            , DateCreated   datetime
                            , DateAccessed  datetime
                            , DateModified  datetime
                            , Attributes    nVarChar(4000)
                            , Size          bigint
                            
                        ")]
    public static IEnumerable dbaudf_DirectoryList2(SqlString rootDir, SqlString wildCard, SqlBoolean subDirectories)
    {
        ArrayList rowsArray = new ArrayList(); // Already implements IEnumerable, so we don't have to
        if(wildCard.IsNull)
            wildCard = "*";
        
        if(subDirectories.IsNull)
            subDirectories= false;
 
        SearchOption so = SearchOption.TopDirectoryOnly;
 
        if(subDirectories == true)
            so = SearchOption.AllDirectories;
 
        DirectorySearch(rootDir, wildCard, subDirectories, rowsArray);
        return rowsArray;
    }
 
    private static void DirectorySearch(string directory, string wildCard, bool subDirectories, ArrayList rowsArray)
    {
        GetFiles(directory, wildCard, rowsArray);
        if (subDirectories)
        {
            foreach (string d in Directory.GetDirectories(directory))
            {
                DirectorySearch(d, wildCard, subDirectories, rowsArray);
            }
        }
    }
 
    private static void GetFiles(string d, string wildCard, ArrayList rowsArray)
    {
        
        foreach (string f in Directory.GetFiles(d, wildCard))
        foreach (GettyImages.Operations.FileData fsi in GettyImages.Operations.FastDirectoryEnumerator.EnumerateFiles(d,wildCard))
        {
            FileInfo fsi = new FileInfo(f);
            char[] s = {'.'};
 
            object[] column = new object[9];
            column[0] = (string)fsi.Name;
            column[1] = (string)fsi.Path;
            column[2] = (Boolean)SqlBoolean.False;
            column[3] = (string)fsi.Name.Split(s)[fsi.Name.Split(s).Length - 1].ToString();
            column[4] = (DateTime)fsi.CreationTime;
            column[5] = (DateTime)fsi.LastAccesTime;
            column[6] = (DateTime)fsi.LastWriteTime;
            column[7] = (string)fsi.Attributes.ToString();
            column[8] = (int)fsi.Size;
 
            rowsArray.Add(column);
        }
    }
 
    private static void FillRowDir(Object obj,
        out SqlString       altname,
        out SqlString       FullPathname,
        out SqlString       directory,
        out SqlString       extension,
        out SqlDateTime     datetimecreate,
        out SqlDateTime     datetimeaccessed,
        out SqlDateTime     datetimemodified,
        out SqlString       Attributes,
        out SqlInt64        size)
    {
        GettyImages.Operations.FileData fsi = (GettyImages.Operations.FileData)obj;
        
        altname             = (string)fsi.Name;
        FullPathname        = (string)fsi.Path;
        directory           = (string)Path.GetDirectoryName(fsi.Path);
        extension           = (string)Path.GetExtension(fsi.Path);
        datetimecreate      = (DateTime)fsi.CreationTime;
        datetimeaccessed    = (DateTime)fsi.LastAccesTime;
        datetimemodified    = (DateTime)fsi.LastWriteTime;
        Attributes          = (string)fsi.Attributes.ToString();
        size                = (SqlInt64)fsi.Size;
 
    }
 



At this point, I realized that reading large directory trees was not very fast so I found some more sample code out there which brought me to this version which enumerates files and directories much quicker.


 
 
    [Microsoft.SqlServer.Server.SqlFunction(
       DataAccess = DataAccessKind.None,
       FillRowMethodName = "FillRowDir",
       IsDeterministic = true,
       Name = "dbaudf_DirectoryList2",
       TableDefinition = @" Name            nvarchar(4000)
                            , FullPathName  nvarchar(4000)
                            , Directory     nvarchar(4000)
                            , Extension     nvarchar(4000)
                            , DateCreated   datetime
                            , DateAccessed  datetime
                            , DateModified  datetime
                            , Attributes    nVarChar(4000)
                            , Size          bigint
                            
                        ")]
    public static IEnumerable dbaudf_DirectoryList2(SqlString rootDir, SqlString wildCard, SqlBoolean subDirectories)
    {
        ArrayList rowsArray = new ArrayList(); // Already implements IEnumerable, so we don't have to
        if(wildCard.IsNull)
            wildCard = "*";
        
        if(subDirectories.IsNull)
            subDirectories= false;
 
        SearchOption so = SearchOption.TopDirectoryOnly;
 
        if(subDirectories == true)
            so = SearchOption.AllDirectories;
 
        return FastDirectoryEnumerator.GetFiles(rootDir.Value,wildCard.Value,so);
    }
 
    private static void FillRowDir(Object obj,
        out SqlString       altname,
        out SqlString       FullPathname,
        out SqlString       directory,
        out SqlString       extension,
        out SqlDateTime     datetimecreate,
        out SqlDateTime     datetimeaccessed,
        out SqlDateTime     datetimemodified,
        out SqlString       Attributes,
        out SqlInt64        size)
    {
        GettyImages.Operations.FileData fsi = (GettyImages.Operations.FileData)obj;
        
        altname             = (string)fsi.Name;
        FullPathname        = (string)fsi.Path;
        directory           = (string)Path.GetDirectoryName(fsi.Path);
        extension           = (string)Path.GetExtension(fsi.Path);
        datetimecreate      = (DateTime)fsi.CreationTime;
        datetimeaccessed    = (DateTime)fsi.LastAccesTime;
        datetimemodified    = (DateTime)fsi.LastWriteTime;
        Attributes          = (string)fsi.Attributes.ToString();
        size                = (SqlInt64)fsi.Size;
 
    }
 





This is the code for the additional Fast File and Directory Enumeration that I call from the function.






 
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;
 
namespace GettyImages.Operations
{
 
 
    /// <summary>
    /// Contains information about a file returned by the 
    /// <see cref="FastDirectoryEnumerator"/> class.
    /// </summary>
    [Serializable]
    public class FileData
    {
        /// <summary>
        /// Attributes of the file.
        /// </summary>
        public readonly FileAttributes Attributes;
 
        /// <summary>
        /// 
        /// </summary>
        public DateTime CreationTime
        {
            get { return this.CreationTimeUtc.ToLocalTime(); }
        }
 
        /// <summary>
        /// File creation time in UTC
        /// </summary>
        public readonly DateTime CreationTimeUtc;
 
        /// <summary>
        /// Gets the last access time in local time.
        /// </summary>
        public DateTime LastAccesTime
        {
            get { return this.LastAccessTimeUtc.ToLocalTime(); }
        }
        
        /// <summary>
        /// File last access time in UTC
        /// </summary>
        public readonly DateTime LastAccessTimeUtc;
 
        /// <summary>
        /// Gets the last access time in local time.
        /// </summary>
        public DateTime LastWriteTime
        {
            get { return this.LastWriteTimeUtc.ToLocalTime(); }
        }
        
        /// <summary>
        /// File last write time in UTC
        /// </summary>
        public readonly DateTime LastWriteTimeUtc;
        
        /// <summary>
        /// Size of the file in bytes
        /// </summary>
        public readonly long Size;
 
        /// <summary>
        /// Name of the file
        /// </summary>
        public readonly string Name;
 
        /// <summary>
        /// Full path to the file.
        /// </summary>
        public readonly string Path;
 
        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return this.Name;
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="FileData"/> class.
        /// </summary>
        /// <param name="dir">The directory that the file is stored at</param>
        /// <param name="findData">WIN32_FIND_DATA structure that this
        /// object wraps.</param>
        internal FileData(string dir, WIN32_FIND_DATA findData) 
        {
            this.Attributes = findData.dwFileAttributes;
 
 
            this.CreationTimeUtc = ConvertDateTime(findData.ftCreationTime_dwHighDateTime, 
                                                findData.ftCreationTime_dwLowDateTime);
 
            this.LastAccessTimeUtc = ConvertDateTime(findData.ftLastAccessTime_dwHighDateTime,
                                                findData.ftLastAccessTime_dwLowDateTime);
 
            this.LastWriteTimeUtc = ConvertDateTime(findData.ftLastWriteTime_dwHighDateTime,
                                                findData.ftLastWriteTime_dwLowDateTime);
 
            this.Size = CombineHighLowInts(findData.nFileSizeHigh, findData.nFileSizeLow);
 
            this.Name = findData.cFileName;
            this.Path = System.IO.Path.Combine(dir, findData.cFileName);
            
        }
 
        private static long CombineHighLowInts(uint high, uint low)
        {
            return (((long)high) << 0x20) | low;
        }
 
        private static DateTime ConvertDateTime(uint high, uint low)
        {
            long fileTime = CombineHighLowInts(high, low);
            return DateTime.FromFileTimeUtc(fileTime);
        }
    }
 
    /// <summary>
    /// Contains information about the file that is found 
    /// by the FindFirstFile or FindNextFile functions.
    /// </summary>
    [Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto), BestFitMapping(false)]
    internal class WIN32_FIND_DATA
    {
        public FileAttributes dwFileAttributes;
        public uint ftCreationTime_dwLowDateTime;
        public uint ftCreationTime_dwHighDateTime;
        public uint ftLastAccessTime_dwLowDateTime;
        public uint ftLastAccessTime_dwHighDateTime;
        public uint ftLastWriteTime_dwLowDateTime;
        public uint ftLastWriteTime_dwHighDateTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public int dwReserved0;
        public int dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
 
        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return "File name=" + cFileName;
        }
    }
 
    /// <summary>
    /// A fast enumerator of files in a directory.  Use this if you need to get attributes for 
    /// all files in a directory.
    /// </summary>
    /// <remarks>
    /// This enumerator is substantially faster than using <see cref="Directory.GetFiles(string)"/>
    /// and then creating a new FileInfo object for each path.  Use this version when you 
    /// will need to look at the attibutes of each file returned (for example, you need
    /// to check each file in a directory to see if it was modified after a specific date).
    /// </remarks>
    public static class FastDirectoryEnumerator
    {
        /// <summary>
        /// Gets <see cref="FileData"/> for all the files in a directory.
        /// </summary>
        /// <param name="path">The path to search.</param>
        /// <returns>An object that implements <see cref="IEnumerable{FileData}"/> and 
        /// allows you to enumerate the files in the given directory.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="path"/> is a null reference (Nothing in VB)
        /// </exception>
        public static IEnumerable<FileData> EnumerateFiles(string path)
        {
            return FastDirectoryEnumerator.EnumerateFiles(path, "*");
        }
 
        /// <summary>
        /// Gets <see cref="FileData"/> for all the files in a directory that match a 
        /// specific filter.
        /// </summary>
        /// <param name="path">The path to search.</param>
        /// <param name="searchPattern">The search string to match against files in the path.</param>
        /// <returns>An object that implements <see cref="IEnumerable{FileData}"/> and 
        /// allows you to enumerate the files in the given directory.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="path"/> is a null reference (Nothing in VB)
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="searchPattern"/> is a null reference (Nothing in VB)
        /// </exception>
        public static IEnumerable<FileData> EnumerateFiles(string path, string searchPattern)
        {
            return FastDirectoryEnumerator.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
        }
 
        /// <summary>
        /// Gets <see cref="FileData"/> for all the files in a directory that 
        /// match a specific filter, optionally including all sub directories.
        /// </summary>
        /// <param name="path">The path to search.</param>
        /// <param name="searchPattern">The search string to match against files in the path.</param>
        /// <param name="searchOption">
        /// One of the SearchOption values that specifies whether the search 
        /// operation should include all subdirectories or only the current directory.
        /// </param>
        /// <returns>An object that implements <see cref="IEnumerable{FileData}"/> and 
        /// allows you to enumerate the files in the given directory.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="path"/> is a null reference (Nothing in VB)
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="searchPattern"/> is a null reference (Nothing in VB)
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="searchOption"/> is not one of the valid values of the
        /// <see cref="System.IO.SearchOption"/> enumeration.
        /// </exception>
        public static IEnumerable<FileData> EnumerateFiles(string path, string searchPattern, SearchOption searchOption)
        {
            if (path == null)
            {
                throw new ArgumentNullException("path");
            }
            if (searchPattern == null)
            {
                throw new ArgumentNullException("searchPattern");
            }
            if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
            {
                throw new ArgumentOutOfRangeException("searchOption");
            }
 
            string fullPath = Path.GetFullPath(path);
 
            return new FileEnumerable(fullPath, searchPattern, searchOption);
        }
 
 
        /// <summary>
        /// Gets <see cref="FileData"/> for all the files in a directory that match a 
        /// specific filter.
        /// </summary>
        /// <param name="path">The path to search.</param>
        /// <param name="searchPattern">The search string to match against files in the path.</param>
        /// <param name="searchOption"></param>
        /// <returns>An object that implements <see cref="IEnumerable{FileData}"/> and 
        /// allows you to enumerate the files in the given directory.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="path"/> is a null reference (Nothing in VB)
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="searchPattern"/> is a null reference (Nothing in VB)
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="searchOption"/> is a null reference (Nothing in VB)
        /// </exception>
        public static FileData[] GetFiles(string path, string searchPattern, SearchOption searchOption)
        {
            IEnumerable<FileData> e = FastDirectoryEnumerator.EnumerateFiles(path, searchPattern, searchOption);
            List<FileData> list = new List<FileData>(e);
 
            FileData[] retval = new FileData[list.Count];
            list.CopyTo(retval);
 
            return retval;
        }
 
        /// <summary>
        /// Provides the implementation of the 
        /// <see cref="T:System.Collections.Generic.IEnumerable`1"/> interface
        /// </summary>
        private class FileEnumerable : IEnumerable<FileData>
        {
            private readonly string m_path;
            private readonly string m_filter;
            private readonly SearchOption m_searchOption;
 
            /// <summary>
            /// Initializes a new instance of the <see cref="FileEnumerable"/> class.
            /// </summary>
            /// <param name="path">The path to search.</param>
            /// <param name="filter">The search string to match against files in the path.</param>
            /// <param name="searchOption">
            /// One of the SearchOption values that specifies whether the search 
            /// operation should include all subdirectories or only the current directory.
            /// </param>
            public FileEnumerable(string path, string filter, SearchOption searchOption)
            {
                m_path = path;
                m_filter = filter;
                m_searchOption = searchOption;
            }
 
            #region IEnumerable<FileData> Members
 
            /// <summary>
            /// Returns an enumerator that iterates through the collection.
            /// </summary>
            /// <returns>
            /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can 
            /// be used to iterate through the collection.
            /// </returns>
            public IEnumerator<FileData> GetEnumerator()
            {
                return new FileEnumerator(m_path, m_filter, m_searchOption);
                
            }
 
            #endregion
 
            #region IEnumerable Members
 
            /// <summary>
            /// Returns an enumerator that iterates through a collection.
            /// </summary>
            /// <returns>
            /// An <see cref="T:System.Collections.IEnumerator"/> object that can be 
            /// used to iterate through the collection.
            /// </returns>
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return new FileEnumerator(m_path, m_filter, m_searchOption);
            }
 
            #endregion
        }
 
        /// <summary>
        /// Wraps a FindFirstFile handle.
        /// </summary>
        private sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            [DllImport("kernel32.dll")]
            private static extern bool FindClose(IntPtr handle);
 
            /// <summary>
            /// Initializes a new instance of the <see cref="SafeFindHandle"/> class.
            /// </summary>
            [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
            internal SafeFindHandle()
                : base(true)
            {
            }
 
            /// <summary>
            /// When overridden in a derived class, executes the code required to free the handle.
            /// </summary>
            /// <returns>
            /// true if the handle is released successfully; otherwise, in the 
            /// event of a catastrophic failure, false. In this case, it 
            /// generates a releaseHandleFailed MDA Managed Debugging Assistant.
            /// </returns>
            protected override bool ReleaseHandle()
            {
                return FindClose(base.handle);
            }
        }
 
        /// <summary>
        /// Provides the implementation of the 
        /// <see cref="T:System.Collections.Generic.IEnumerator`1"/> interface
        /// </summary>
        [System.Security.SuppressUnmanagedCodeSecurity]
        private class FileEnumerator : IEnumerator<FileData>
        {
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern SafeFindHandle FindFirstFile(string fileName, 
                [In, Out] WIN32_FIND_DATA data);
 
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern bool FindNextFile(SafeFindHandle hndFindFile, 
                    [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_DATA lpFindFileData);
 
            /// <summary>
            /// Hold context information about where we current are in the directory search.
            /// </summary>
            private class SearchContext
            {
                public readonly string Path;
                public Stack<string> SubdirectoriesToProcess;
 
                public SearchContext(string path)
                {
                    this.Path = path;
                }
            }
 
            private string m_path;
            private string m_filter;
            private SearchOption m_searchOption;
            private Stack<SearchContext> m_contextStack;
            private SearchContext m_currentContext;
 
            private SafeFindHandle m_hndFindFile;
            private WIN32_FIND_DATA m_win_find_data = new WIN32_FIND_DATA();
 
            /// <summary>
            /// Initializes a new instance of the <see cref="FileEnumerator"/> class.
            /// </summary>
            /// <param name="path">The path to search.</param>
            /// <param name="filter">The search string to match against files in the path.</param>
            /// <param name="searchOption">
            /// One of the SearchOption values that specifies whether the search 
            /// operation should include all subdirectories or only the current directory.
            /// </param>
            public FileEnumerator(string path, string filter, SearchOption searchOption)
            {
                m_path = path;
                m_filter = filter;
                m_searchOption = searchOption;
                m_currentContext = new SearchContext(path);
                
                if (m_searchOption == SearchOption.AllDirectories)
                {
                    m_contextStack = new Stack<SearchContext>();
                }
            }
 
            #region IEnumerator<FileData> Members
 
            /// <summary>
            /// Gets the element in the collection at the current position of the enumerator.
            /// </summary>
            /// <value></value>
            /// <returns>
            /// The element in the collection at the current position of the enumerator.
            /// </returns>
            public FileData Current
            {
                get { return new FileData(m_path, m_win_find_data); }
            }
 
            #endregion
 
            #region IDisposable Members
 
            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, 
            /// or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            {
                if (m_hndFindFile != null)
                {
                    m_hndFindFile.Dispose();
                }
            }
 
            #endregion
 
            #region IEnumerator Members
 
            /// <summary>
            /// Gets the element in the collection at the current position of the enumerator.
            /// </summary>
            /// <value></value>
            /// <returns>
            /// The element in the collection at the current position of the enumerator.
            /// </returns>
            object System.Collections.IEnumerator.Current
            {
                get { return new FileData(m_path, m_win_find_data); }
            }
 
            /// <summary>
            /// Advances the enumerator to the next element of the collection.
            /// </summary>
            /// <returns>
            /// true if the enumerator was successfully advanced to the next element; 
            /// false if the enumerator has passed the end of the collection.
            /// </returns>
            /// <exception cref="T:System.InvalidOperationException">
            /// The collection was modified after the enumerator was created.
            /// </exception>
            public bool MoveNext()
            {
                bool retval = false;
 
                //If the handle is null, this is first call to MoveNext in the current 
                // directory.  In that case, start a new search.
                if (m_currentContext.SubdirectoriesToProcess == null)
                {
                    if (m_hndFindFile == null)
                    {
                        new FileIOPermission(FileIOPermissionAccess.PathDiscovery, m_path).Demand();
 
                        string searchPath = Path.Combine(m_path, m_filter);
                        m_hndFindFile = FindFirstFile(searchPath, m_win_find_data);
                        retval = !m_hndFindFile.IsInvalid;
                    }
                    else
                    {
                        //Otherwise, find the next item.
                        retval = FindNextFile(m_hndFindFile, m_win_find_data);
                    }
                }
 
                //If the call to FindNextFile or FindFirstFile succeeded...
                if (retval)
                {
                    if (((FileAttributes)m_win_find_data.dwFileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
                    {
                        //Ignore folders for now.   We call MoveNext recursively here to 
                        // move to the next item that FindNextFile will return.
                        return MoveNext();
                    }
                }
                else if (m_searchOption == SearchOption.AllDirectories)
                {
                    //SearchContext context = new SearchContext(m_hndFindFile, m_path);
                    //m_contextStack.Push(context);
                    //m_path = Path.Combine(m_path, m_win_find_data.cFileName);
                    //m_hndFindFile = null;
 
                    if (m_currentContext.SubdirectoriesToProcess == null)
                    {
                        string[] subDirectories = null;
                        try
                        {
                            subDirectories = Directory.GetDirectories(m_path);
                        }
                        catch {}
 
                        if(subDirectories != null && subDirectories.Length != 0)
                            m_currentContext.SubdirectoriesToProcess = new Stack<string>(subDirectories);
                    }
 
                    if (m_currentContext.SubdirectoriesToProcess != null)
                    {
                        if (m_currentContext.SubdirectoriesToProcess.Count > 0)
                        {
                            string subDir = m_currentContext.SubdirectoriesToProcess.Pop();
 
                            m_contextStack.Push(m_currentContext);
                            m_path = subDir;
                            m_hndFindFile = null;
                            m_currentContext = new SearchContext(m_path);
                            return MoveNext();
                        }
                    }
 
                    //If there are no more files in this directory and we are 
                    // in a sub directory, pop back up to the parent directory and
                    // continue the search from there.
                    if (m_contextStack.Count > 0)
                    {
                        m_currentContext = m_contextStack.Pop();
                        m_path = m_currentContext.Path;
                        if (m_hndFindFile != null)
                        {
                            m_hndFindFile.Close();
                            m_hndFindFile = null;
                        }
 
                        return MoveNext();
                    }
                }
 
                return retval;
            }
 
            /// <summary>
            /// Sets the enumerator to its initial position, which is before the first element in the collection.
            /// </summary>
            /// <exception cref="T:System.InvalidOperationException">
            /// The collection was modified after the enumerator was created.
            /// </exception>
            public void Reset()
            {
                m_hndFindFile = null;
            }
 
            #endregion
        }
    }
 
}
 
 

No comments:

Post a Comment