Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions src/ModularPipelines/FileSystem/Folder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ public void Delete()
/// Removes all files and subdirectories within the folder.
/// </summary>
/// <remarks>
/// This method preserves backward compatibility by not removing read-only attributes.
/// Use <see cref="Clean(bool)"/> with <c>removeReadOnlyAttribute: true</c> to handle read-only files.
/// This method preserves backward compatibility by not removing read-only attributes and failing on first error.
/// Use <see cref="Clean(bool, bool)"/> for more control over error handling.
/// </remarks>
public void Clean()
{
Clean(removeReadOnlyAttribute: false);
Clean(removeReadOnlyAttribute: false, continueOnError: false);
}

/// <summary>
Expand All @@ -123,27 +123,71 @@ public void Clean()
/// This helps handle read-only items that would otherwise fail to delete.
/// </param>
public void Clean(bool removeReadOnlyAttribute)
{
Clean(removeReadOnlyAttribute, continueOnError: false);
}

/// <summary>
/// Removes all files and subdirectories within the folder.
/// </summary>
/// <param name="removeReadOnlyAttribute">
/// When true, removes the read-only attribute from files and directories before deletion.
/// This helps handle read-only items that would otherwise fail to delete.
/// </param>
/// <param name="continueOnError">
/// When true, continues deleting remaining items even if some deletions fail.
/// Failed deletions are logged and aggregated into a single exception at the end.
/// When false, the first error encountered will stop the operation and throw immediately.
/// </param>
/// <exception cref="AggregateException">
/// Thrown when <paramref name="continueOnError"/> is true and one or more deletions failed.
/// Contains all individual exceptions encountered during the operation.
/// </exception>
public void Clean(bool removeReadOnlyAttribute, bool continueOnError)

Copilot AI Jan 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new continueOnError parameter lacks test coverage. Consider adding tests that verify:

  1. When continueOnError: true, the method continues deletion after encountering an error (e.g., by creating a file locked by another process)
  2. When continueOnError: true, failed deletions result in an AggregateException containing all the individual errors
  3. When continueOnError: false (default), the method stops and throws on the first error as expected

The existing test file test/ModularPipelines.UnitTests/FolderTests.cs has tests for CleanFiles() and CleanFolders(), making it an appropriate location for these new test cases.

Copilot uses AI. Check for mistakes.
{
LogFolderOperation("Cleaning Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this);

var errors = new List<Exception>();

foreach (var directory in DirectoryInfo.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
{
if (removeReadOnlyAttribute)
try
{
RemoveReadOnlyAttributeRecursively(directory);
}
if (removeReadOnlyAttribute)
{
RemoveReadOnlyAttributeRecursively(directory);
}

directory.Delete(true);
directory.Delete(true);
}
catch (Exception ex) when (continueOnError)
{
LogFolderWarning(ex, "Failed to delete directory: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", directory.FullName);
errors.Add(ex);
}
}

foreach (var file in DirectoryInfo.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
{
if (removeReadOnlyAttribute && (file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
try
{
file.Attributes &= ~FileAttributes.ReadOnly;
if (removeReadOnlyAttribute && (file.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
file.Attributes &= ~FileAttributes.ReadOnly;
}

file.Delete();
}
catch (Exception ex) when (continueOnError)
{
LogFolderWarning(ex, "Failed to delete file: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", file.FullName);
errors.Add(ex);
}
}

file.Delete();
if (errors.Count > 0)
{
throw new AggregateException($"Failed to delete {errors.Count} item(s) in folder {Path}", errors);
}
}

Expand Down
Loading