Mayur Jadhav
Mayur Jadhav

Reputation: 43

C# - How to generate a list of file names and sub directories in a directory with creation date

I am trying to generate a list of all the files present in a directory and its sub-directories along with their creation date. The date and file name to be separated by "::"

I tried the below code but its taking time when the number of files are huge. Can anyone suggest me a better approach/ optimise the code??

I am even getting the "Access to the file denied" exception with the below approach, for some of the files in C Drive.

DirectoryInfo dir1 = new DirectoryInfo(path);
DirectoryInfo[] dir = dir1.GetDirectories();
StreamWriter write = new StreamWriter("Test.lst");
foreach (DirectoryInfo di in dir)
{
    try
    {
        FileInfo[] file = di.GetFiles("*", SearchOption.AllDirectories);

        foreach (var f in file)
        {
            write.WriteLine(f.FullName + "::" + f.CreationTime.ToShortDateString());
        }
    }
    catch
    {
    }
}
write.Flush();
write.Close();

Upvotes: 2

Views: 4060

Answers (3)

Josh
Josh

Reputation: 44906

You really should use the new EnumerateFiles overloads in order to avoid getting the entire list in memory.

foreach (var f in di.EnumerateFiles("*", SearchOption.AllDirectories)) {
    write.WriteLine(f.FullName + "::" + f.CreationTime.ToShortDateString());
}

You can further optimize your code by writing each line as it is enumerated over. You can avoid the stream manipulation all together. Your entire routine becomes just a few lines:

public static void GenerateList(String dirPath, String fileName) {
    var dir1 = new DirectoryInfo(dirPath);

    try {
        var lines = from f in dir1.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
                    select f.FullName + "::" + f.CreationTime.ToShortDateString();

        File.WriteAllLines(fileName, lines);
    }
    catch (Exception ex) {
        Console.WriteLine(ex);
    }
}

UPDATE

Ok, so not having .Net 4.0 is a bummer. However, you could write your own class to enumerate a file system without too much trouble. Here is one I just wrote that you can play with, and it only uses API calls that are already available to .Net 3.5

public class FileSystemInfoEnumerator: IEnumerator<FileSystemInfo>, IEnumerable<FileSystemInfo> {
  private const string DefaultSearchPattern = "*.*";

  private String InitialPath { get; set; }
  private String SearchPattern { get; set; }
  private SearchOption SearchOptions { get; set; }
  private Stack<IEnumerator<FileSystemInfo>> EnumeratorStack { get; set; }

  private Action<Exception> ErrorHandler { get; set; }

  public FileSystemInfoEnumerator(String path, String pattern = DefaultSearchPattern, SearchOption searchOption = SearchOption.TopDirectoryOnly, Action<Exception> errorHandler = null) {

     if (String.IsNullOrWhiteSpace(path))
        throw new ArgumentException("path cannot be null or empty");

     var dirInfo = new DirectoryInfo(path);

     if(!dirInfo.Exists)
        throw new InvalidOperationException(String.Format("File or Directory \"{0}\" does not exist", dirInfo.FullName));

     InitialPath = dirInfo.FullName;
     SearchOptions = searchOption;

     if(String.IsNullOrWhiteSpace(pattern)) {
        pattern = DefaultSearchPattern;
     }

     ErrorHandler = errorHandler ?? DefaultErrorHandler;
     EnumeratorStack = new Stack<IEnumerator<FileSystemInfo>>();
     SearchPattern = pattern;
     EnumeratorStack.Push(GetDirectoryEnumerator(new DirectoryInfo(InitialPath)));
  }

  private void DefaultErrorHandler(Exception ex) {
     throw ex;
  }

  private IEnumerator<FileSystemInfo> GetDirectoryEnumerator(DirectoryInfo directoryInfo) {
     var infos = new List<FileSystemInfo>();

     try {
        if (directoryInfo != null) {
           var info = directoryInfo.GetFileSystemInfos(SearchPattern);
           infos.AddRange(info);
        }
     } catch (Exception ex) {
        ErrorHandler(ex);
     }

     return infos.GetEnumerator();
  }

  public void Dispose() {
     foreach (var enumerator in EnumeratorStack) {
        enumerator.Reset();
        enumerator.Dispose();
     }
  }

  public bool MoveNext() {
     var current = Current;

     if (ShouldRecurse(current)) {
        EnumeratorStack.Push(GetDirectoryEnumerator(current as DirectoryInfo));
     }

     var moveNextSuccess = TopEnumerator.MoveNext();

     while(!moveNextSuccess && TopEnumerator != null) {
        EnumeratorStack.Pop();

        moveNextSuccess = TopEnumerator != null && TopEnumerator.MoveNext();
     }

     return moveNextSuccess;
  }

  public void Reset() {
     EnumeratorStack.Clear();
     EnumeratorStack.Push(GetDirectoryEnumerator(new DirectoryInfo(InitialPath)));
  }

  public FileSystemInfo Current {
     get {
        return TopEnumerator.Current;
     }
  }

  object IEnumerator.Current {
     get {
        return Current;
     }
  }

  public IEnumerator<FileSystemInfo> GetEnumerator() {
     return this;
  }

  IEnumerator IEnumerable.GetEnumerator() {
     return GetEnumerator();
  }

  IEnumerator<FileSystemInfo> TopEnumerator {
     get {
        if(EnumeratorStack.Count > 0)
           return EnumeratorStack.Peek();

        return null;
     }
  }

  private Boolean ShouldRecurse(FileSystemInfo current) {
     return current != null &&
            IsDirectory(current) &&
            SearchOptions == SearchOption.AllDirectories;
  }

  private Boolean IsDirectory(FileSystemInfo fileSystemInfo) {
     return fileSystemInfo != null &&
            (fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
  }
}

Using it is pretty easy, just instantiate it with the options you want, and then use it like any IEnumerable.

var fileSystemEnumerator = new FileSystemInfoEnumerator("C:\\Dir", 
    searchOption: SearchOption.AllDirectories,
    errorHandler: Console.WriteLine);

var lines = from f in fileSystemEnumerator
            select f.FullName + "::" + f.CreationTime.ToShortDateString();

File.WriteAllLines("FileNames.txt", lines);

Now obviously this isn't as efficient as the .Net 4.0 one, but the memory footprint should be acceptable. I tested this on a directory with 50K+ files and it finished in about 5 seconds.

Hope this helps you out!

Upvotes: 1

Thorsten Hans
Thorsten Hans

Reputation: 2683

Or when you're just lookin for the path you can do

Directory.GetFiles("C:\\", "*", SearchOption.AllDirectories)
                .ToList()
                .ForEach(Console.WriteLine);

Upvotes: 1

Joel Lucsy
Joel Lucsy

Reputation: 8706

var dirinfo = new DirectoryInfo( "c:\path" );
var entries = dirinfo.GetFileSystemInfos( "*.*", SearchOption.AllDirectories )
    .Select( t => string.Format( "{0}::{1}", t.FullName, t.CreationTime );

Upvotes: 2

Related Questions