Skip to content

Add a delegate for OnDirectoryFinished() and others #1953

@iSazonov

Description

@iSazonov

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 be OnDirectoryFinishedDelegate(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?).

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-System.IO

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions