-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Background and motivation
Developers need a way to handle events while recursing directories with FileSystemEnumerable. The FileSystemEnumerator API already gives the ability to do this by exposing ContinueOnError(int), OnDirectoryFinished(ROS<char>), ShouldIncludeEntry(FilesystemEntry), and ShouldRecurseIntoEntry(FileSystemEntry). The last two methods can be easily used with the FileSystemEnumerable API as it allows you set delegates for them. This proposal is about completing the scenario by adding two more delegate properties for the remaining two.
Use case description:
In PowerShell Core repo we are trying to migrate to FileSystemEnumerable.
We need to implement error handling so that continue processing on an error and logging the error.
Also for consistency it would be great to get OnDirectoryFinishedDelegate.
Proposed API
public class FileSystemEnumerable<TResult> : IEnumerable<TResult>
{
public FindPredicate? ShouldIncludePredicate { get; set; } // Existing API
public FindPredicate? ShouldRecursePredicate { get; set; } // Existing API
public delegate void OnDirectoryFinishedDelegate(ReadOnlySpan<char> directory); // New API
public OnDirectoryFinishedDelegate? OnDirectoryFinishedAction { get; set; } // New API
public delegate bool ContinueOnErrorPredicate(int error); // New API
public ContinueOnErrorPredicate? ShouldContinueOnErrorPredicate { get; set; } // New API
}API usage
using System.IO.Enumeration;
string directory = "...";
var context = ... // PowerShell context
var files = new FileSystemEnumerable<string>(
directory,
// Yes, it is weird but we should write right error for root directory too.
(ref FileSystemEntry entry) => entry.OriginalRootDirectory.Length > entry.Directory.Length ? entry.FileName.ToString() : Path.Join(entry.Directory.Slice(entry.OriginalRootDirectory.Length), entry.FileName),
options)
{
ShouldContinueOnErrorPredicate = (int error) =>
{
if (error == 5) // ERROR_ACCESS_DENIED
{
// Log the error...
}
if (error == 3) // ERROR_PATH_NOT_FOUND
{
return false;
}
return true;
}
};Alternative Designs: define FileSystemErrorInfo to abstract the OS native error (this differs from FileSystemEnumerator<T>.ContinueOnError(int)).
public class FileSystemEnumerable<TResult> : IEnumerable<TResult>
{
public FindPredicate? ShouldIncludePredicate { get; set; } // Existing API
public FindPredicate? ShouldRecursePredicate { get; set; } // Existing API
public delegate void OnDirectoryFinishedDelegate(ReadOnlySpan<char> directory); // New API
public OnDirectoryFinishedDelegate? OnDirectoryFinishedAction { get; set; } // New API
public delegate bool ContinueOnErrorPredicate(FileSystemErrorInfo errorInfo); // New API
public ContinueOnErrorPredicate? ShouldContinueOnErrorPredicate { get; set; } // New API
}
public readonly ref struct FileSystemErrorInfo
{
// internal FileSystemErrorInfo(int errno, string path);
public int Error { get; }; // 'errno' is converted on Unix with `internal struct ErrorInfo` to platform-agnostic Error
public string Path { get; };
public Exception GetException(); // Gets platform-agnostic exception for the error
public string GetErrorMessage(); // Optional for me but could be useful due to absence of extra allocations with GetException()
}
public unsafe abstract partial class FileSystemEnumerator<TResult> : CriticalFinalizerObject, IEnumerator<TResult>
{
[Obsolete("The method is deprecated. Use ContinueOnError(FileSystemErrorInfo errorInfo) instead.")] // New API
protected virtual bool ContinueOnError(int error); // Deprecate
protected virtual bool ContinueOnError(FileSystemErrorInfo errorInfo); // New API
}Remarkable questions
- Should we use
events? - Should
OnDirectoryFinishedDelegate(ROS<char>)should beOnDirectoryFinishedDelegate(FilesystemEntry)
This would differ from what the Enumerator type exposes; implementation-wise we would need to create an extra FileSystemEntry that represents the root directory (could impact perf?).