-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a delegate for OnDirectoryFinished() and others #1953
Comments
This is not the first time I've been asked for this and it is pretty straight-forward, I think we should add this for 5.0. |
For full consistency I'd add a delegate for |
@iSazonov How would the "depth limit" scenario work with these predicates? In the implementation of ShouldRecursePredicate would it examine the FileSystemEntry and then calculate the depth vs the start of the recursion (perhaps with Path.GetRelativePath and counting the double dots?) As with any API proposal it might be useful to write out what the user code would look like. |
@danmosemsft You can see draft code in PowerShell repo PowerShell/PowerShell#12834. The PR demonstrates well a real scenario of using the API. |
@iSazonov to make the API review meetings efficient we ask that all the supporting info be in the proposal (ideally in the top post). For API proposals this ideally includes small usage examples to help demo how the API would be used |
@danmosemsft I added a code snipped from the PowerShell PR. |
Thanks, that makes it clear. |
namespace System.IO.Enumeration
{
public partial class FileSystemEnumerable<TResult> : IEnumerable<TResult>
{
public delegate void OnDirectoryFinishedDelegate(ref FileSystemEntry entry);
public delegate bool ContinueOnErrorPredicate(ref FileSystemEntry parent, ReadOnlySpan<char> entry, int error);
public OnDirectoryFinishedDelegate? OnDirectoryFinishedAction { get; set; }
public ContinueOnErrorPredicate? ShouldContinueOnErrorPredicate { get; set; }
}
} |
OnDirectoryFinished is defined as runtime/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.cs Line 63 in 6072e4d
and my proposal follows this. If MSFT team want enhance this - I agree.
ContinueOnError is defined as runtime/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.cs Line 70 in 6072e4d
and my proposal follows this. If MSFT team want enhance this - I agree. |
Can we continue this work? I still hope to use this in the next PowerShell milestone (LTS!). I have updated the proposal based on previous discussion and also added a new option to EnumerationOptions to support recursion depth limit. The current implementation uses a queue to process subdirectories. This allows us to implement the recursion depth limit in the simplest way so I don't need to invent tricky code in PowerShell. I could try to pull a PR and implement the part as only the proposal will be approved. |
Thank you, @iSazonov.
The parameters of Another thing that was mentioned is that we need to make sure the reported error makes sense across platforms. Windows does not throw the same errors as Linux, but we need to reflect that in our APIs in an OS independent way.
Please don't forget to add new code usage examples to help understand how the modified API structure ties up together. cc @jozkee |
@carlossanlop Thanks for clarification! My today priority is the recursion depth limit, it blocks me. The recursion depth limit API proposal doesn't seem to correlate with other proposed APIs so maybe move it in new issue to move forward faster? |
If you don't think they depend will each other, then sure, then I don't mind if we address that API separately. But for simplicity and clarity, since the other APIs are not ready yet, can you please move the MaxDepthRecursion API to a separate API Proposal issue? |
@carlossanlop I put new proposal to #43748. |
I have a concern about As for generalization error reporting, have we any samples of such maooings in the repo? |
@iSazonov Can we remove |
@jozkee Done. |
It seems this is waiting me :-) If we look current implementation we can get only two actual entities in error time - (1) native error number, (2) current path (on which the error is raised), and we haven't FileSystemEntry in the time. So proposal is namespace System.IO.Enumeration
{
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
}
} |
@danmoseley @jozkee Friendly ping. |
I'd happy to implement this for PowerShell. |
For For @iSazonov if you are blocked by this, you think you can extend the Enumerator and work with that? e.g: class MyFileSystmeEnumerator : FileSystemEnumerator<string>
{
public MyFileSystmeEnumerator(string directory, EnumerationOptions? options = null) : base(directory, options) { }
protected override string TransformEntry(ref FileSystemEntry entry)
{
return entry.ToFullPath();
}
protected override void OnDirectoryFinished(ReadOnlySpan<char> directory)
{
// Do what you need
}
protected override bool ContinueOnError(int error)
{
// Do what you need
}
} |
If we look current implementation there is no FileSystemEntry instance in points where the error is detected. And I don't see needs to create the instance for "parent" path in the error report (user himself can create Directory/FileInfo if needed) - we can easily have parent path but what about error code - it will OS specific but we need OS agnostic. As for GetException() and GetErrorMessage(), these address previous API review request to have OS agnostic error report since today we get an OS specific Int value. We could remove FileSystemErrorInfo from the API proposal and return Exception which contains the native error code, but we lost parent path since it will be in error message only. So we again come back to previous API review and suggestion to have new (extendable) type for error reporting.
I can not process ContinueOnError(int error) well and as result the issue was opened :-) |
That's interesting because it contradicts the current The trade-off with handling @iSazonov I know you originally opened with the purpose of handling Max Depth, which is now supported by |
Sorry, it seems I don't understand your question. If I remember right Jeremy made a proposal for file enumeration with a lot of new APIs, some from them (including ContinueOnError) was under "need feedback from community". First feedback (as Jeremy mentioned) was community want delegates for all virtual methods so I added OnDirectoryFinishedDelegate in the proposal.
In my old PR PowerShell/PowerShell#12834 you can see how weird looks code (notice, I don't OS specific error code processing there for the example simplicity!) with ContinueOnError(int error) - with ContinueOnError(FileSystemErrorInfo errorInfo) we get exactly we need. |
I agree this proposal is about adding delegates for all virtual in the Enumerator and set them as properties in the Enumerable. What I am pointing out is that the virtual method So I would suggest having FileSystemErrorInfo as an alternative design.
Could you please write a minimal sample in here? It would be hard to scroll through your PR while reviewing the proposal. |
Yes, of cause. 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)
{
ShouldIncludePredicate = (ref FileSystemEntry entry) => FileSystemEntryFilter, // PowerSHell include filter delegate
ShouldRecursePredicate = (ref FileSystemEntry entry) =>
{
if (context.Recurce && context.IsFollowSymlink)
{
// Follow symlinks at recursion.
return entry.Attributes.HasFlag(FileAttributes.ReparsePoint) && InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(entry.ToFileSystemInfo());
}
return context.Recurce;
},
ShouldContinueOnErrorPredicate = (FileSystemErrorInfo errorInfo) =>
{
var exc = errorInfo.GetException();
if (exc is UnauthorizedAccessException)
{
context.WriteError(new ErrorRecord(exc, "DirUnauthorizedAccessError", ErrorCategory.PermissionDenied, errorInfo.Path));
}
else
{
context.WriteError(new ErrorRecord(exc, "DirIOError", ErrorCategory.ReadError, errorInfo.Path));
}
return true;
}
}; |
Action Required:
public class FileSystemEnumerable<TResult> : IEnumerable<TResult>
{
public Action<string>? DirectoryStartedAction { get; set; } // New API
public Action<string>? DirectoryFinishedAction { get; set; } // New API
public Func<int, bool>? ShouldContinueOnErrorPredicate { get; set; } // New API
} |
I agree it is important to pass current context to custom error handler. runtime/src/libraries/System.Private.CoreLib/src/System/IO/Enumeration/FileSystemEnumerator.Unix.cs Lines 69 to 82 in 2f3fcca
with private IntPtr CreateDirectoryHandle(string path, bool ignoreNotFound = false)
{
IntPtr handle = Interop.Sys.OpenDir(path);
if (handle == IntPtr.Zero)
{
Interop.ErrorInfo info = Interop.Sys.GetLastErrorInfo();
if (InternalContinueOnError(info, ignoreNotFound))
{
return IntPtr.Zero;
}
var exception = Interop.GetExceptionForIoErrno(info, path, isDirError: true)
if (ContinueOnError(exception))
{
return IntPtr.Zero;
}
throw exception;
}
return handle;
} This is consistent with the way PowerShell works - catch an exception and print a friendly message to the user, i.e. the exception instance is used anyway. Current proposal: namespace System.IO.Enumeration
{
public class FileSystemEnumerable<TResult> : IEnumerable<TResult>
{
public FindPredicate? ShouldIncludePredicate { get; set; } // Existing API
public FindPredicate? ShouldRecursePredicate { get; set; } // Existing API
public Action<string>? DirectoryStartedAction { get; set; } // New API
public Action<string>? DirectoryFinishedAction { get; set; } // New API
public Func<Exception, bool>? ShouldContinueOnErrorPredicate { get; set; } // New API
}
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 or no?
protected virtual bool ContinueOnError(Exception exception); // New API
}
} |
I understand that this thread still needs some work before it can be reviewed again. |
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)
, andShouldRecurseIntoEntry(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
API usage
Alternative Designs: define FileSystemErrorInfo to abstract the OS native error (this differs from
FileSystemEnumerator<T>.ContinueOnError(int)
).Remarkable questions
event
s?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?).
The text was updated successfully, but these errors were encountered: