-
-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Add async overloads for File and Folder operations #1722
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -163,6 +163,19 @@ public File Create() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Asynchronously creates a new file at the current path. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>This file instance for method chaining.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async Task<File> CreateAsync() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogFileOperation("Creating File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var fileStream = System.IO.File.Create(Path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await fileStream.DisposeAsync().ConfigureAwait(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <inheritdoc cref="FileSystemInfo.Attributes"/>> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public FileAttributes Attributes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -192,6 +205,21 @@ public void Delete() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| FileInfo.Delete(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Asynchronously deletes the file. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Uses thread pool offloading as no native async delete API exists in .NET. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// For true async I/O, consider using stream-based operations where available. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Cancellation token.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Task DeleteAsync(CancellationToken cancellationToken = default) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+212
to
+217
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogFileOperation("Deleting File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Task.Run(() => FileInfo.Delete(), cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+212
to
+220
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Uses thread pool offloading as no native async delete API exists in .NET. | |
| /// For true async I/O, consider using stream-based operations where available. | |
| /// </remarks> | |
| /// <param name="cancellationToken">Cancellation token.</param> | |
| public Task DeleteAsync(CancellationToken cancellationToken = default) | |
| { | |
| LogFileOperation("Deleting File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); | |
| return Task.Run(() => FileInfo.Delete(), cancellationToken); | |
| /// No native async delete API exists in .NET; this method performs a synchronous | |
| /// delete and returns a completed task. For true async I/O, consider using | |
| /// stream-based operations where available. | |
| /// </remarks> | |
| /// <param name="cancellationToken">Cancellation token.</param> | |
| public Task DeleteAsync(CancellationToken cancellationToken = default) | |
| { | |
| cancellationToken.ThrowIfCancellationRequested(); | |
| LogFileOperation("Deleting File: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); | |
| FileInfo.Delete(); | |
| return Task.CompletedTask; |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new MoveToAsync method lacks test coverage. Since the synchronous MoveTo method has comprehensive test coverage in FileTests.cs, the async version should have corresponding tests to verify it works correctly, properly handles cancellation tokens, and updates the file path as expected.
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation wraps a synchronous operation (FileInfo.MoveTo) in Task.Run, which contradicts the PR's goal of preventing thread pool starvation. File move operations are typically fast metadata operations that don't benefit from being wrapped in Task.Run, and doing so actually increases thread pool pressure.
| /// Uses thread pool offloading as no native async move API exists in .NET. | |
| /// </remarks> | |
| /// <param name="path">The destination path.</param> | |
| /// <param name="cancellationToken">Cancellation token.</param> | |
| /// <returns>This file instance for method chaining.</returns> | |
| public Task<File> MoveToAsync(string path, CancellationToken cancellationToken = default) | |
| { | |
| LogFileOperationWithDestination("Moving File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); | |
| return Task.Run(() => | |
| { | |
| FileInfo.MoveTo(path); | |
| return this; | |
| }, cancellationToken); | |
| /// Executes the move synchronously as file moves are typically fast metadata operations. | |
| /// </remarks> | |
| /// <param name="path">The destination path.</param> | |
| /// <param name="cancellationToken">Cancellation token.</param> | |
| /// <returns>This file instance for method chaining.</returns> | |
| public Task<File> MoveToAsync(string path, CancellationToken cancellationToken = default) | |
| { | |
| cancellationToken.ThrowIfCancellationRequested(); | |
| LogFileOperationWithDestination("Moving File: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); | |
| FileInfo.MoveTo(path); | |
| return Task.FromResult(this); |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new MoveToAsync(Folder) overload lacks test coverage. Since the synchronous MoveTo(Folder) method has test coverage in FileTests.cs, the async version should have corresponding tests to verify it correctly creates the destination folder and moves the file.
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file streams are not properly disposed if an exception occurs between opening the source stream and the await using block. Consider declaring the variable inside the await using statement or using a try-finally block to ensure proper disposal in all error cases.
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new CopyToAsync(Folder) overload lacks test coverage. Since the synchronous CopyTo(Folder) method has test coverage in FileTests.cs, the async version should have corresponding tests to verify it correctly creates the destination folder and copies the file with proper content preservation.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -96,13 +96,46 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Asynchronously creates the folder if it does not exist. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Uses thread pool offloading as no native async directory creation API exists in .NET. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Optional cancellation token.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>This folder instance for method chaining.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Task<Folder> CreateAsync(CancellationToken cancellationToken = default) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogFolderOperation("Creating Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Task.Run(() => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Directory.CreateDirectory(Path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void Delete() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogFolderOperation("Deleting Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DirectoryInfo.Delete(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Asynchronously deletes the folder and all its contents. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Uses thread pool offloading as no native async delete API exists in .NET. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Cancellation token.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+131
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Task DeleteAsync(CancellationToken cancellationToken = default) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogFolderOperation("Deleting Folder: {Path} [Module: {ModuleName}, Activity: {ActivityId}]", this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Task.Run(() => DirectoryInfo.Delete(true), cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Removes all files and subdirectories within the folder. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -217,10 +250,10 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Directory.CreateDirectory(targetPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copy all subdirectories first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var dirPath in Directory.EnumerateDirectories(this, "*", SearchOption.AllDirectories)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 253 in src/ModularPipelines/FileSystem/Folder.cs
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var sourceDir = new DirectoryInfo(dirPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var relativePath = System.IO.Path.GetRelativePath(this, dirPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 256 in src/ModularPipelines/FileSystem/Folder.cs
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var newPath = System.IO.Path.Combine(targetPath, relativePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var targetDir = Directory.CreateDirectory(newPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -236,10 +269,10 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copy all files | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var filePath in Directory.EnumerateFiles(this, "*", SearchOption.AllDirectories)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 272 in src/ModularPipelines/FileSystem/Folder.cs
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var sourceFile = new FileInfo(filePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var relativePath = System.IO.Path.GetRelativePath(this, filePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 275 in src/ModularPipelines/FileSystem/Folder.cs
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var newPath = System.IO.Path.Combine(targetPath, relativePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.IO.File.Copy(filePath, newPath, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -270,6 +303,91 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Folder(targetPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Asynchronously copies the folder and its contents to the specified target path using stream-based file copying. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="targetPath">The destination path for the copied folder.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Cancellation token.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>A new <see cref="Folder"/> instance representing the copied folder.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Task<Folder> CopyToAsync(string targetPath, CancellationToken cancellationToken = default) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return CopyToAsync(targetPath, preserveTimestamps: false, cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Asynchronously copies the folder and its contents to the specified target path using stream-based file copying. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="targetPath">The destination path for the copied folder.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="preserveTimestamps"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// When true, preserves CreationTimeUtc, LastWriteTimeUtc, and LastAccessTimeUtc | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// for all files and directories. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Cancellation token.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>A new <see cref="Folder"/> instance representing the copied folder.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async Task<Folder> CopyToAsync(string targetPath, bool preserveTimestamps, CancellationToken cancellationToken = default) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogFolderOperationWithDestination("Copying Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, targetPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Directory.CreateDirectory(targetPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copy all subdirectories first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var dirPath in Directory.EnumerateDirectories(this, "*", SearchOption.AllDirectories)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 334 in src/ModularPipelines/FileSystem/Folder.cs
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancellationToken.ThrowIfCancellationRequested(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var sourceDir = new DirectoryInfo(dirPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var relativePath = System.IO.Path.GetRelativePath(this, dirPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check warning on line 339 in src/ModularPipelines/FileSystem/Folder.cs
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var newPath = System.IO.Path.Combine(targetPath, relativePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var targetDir = Directory.CreateDirectory(newPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetDir.Attributes = sourceDir.Attributes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (preserveTimestamps) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+328
to
+345
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetDir.CreationTimeUtc = sourceDir.CreationTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetDir.LastWriteTimeUtc = sourceDir.LastWriteTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetDir.LastAccessTimeUtc = sourceDir.LastAccessTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copy all files using async stream copying | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var filePath in Directory.EnumerateFiles(this, "*", SearchOption.AllDirectories)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cancellationToken.ThrowIfCancellationRequested(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var sourceFile = new FileInfo(filePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var relativePath = System.IO.Path.GetRelativePath(this, filePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var newPath = System.IO.Path.Combine(targetPath, relativePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await using var sourceStream = System.IO.File.OpenRead(filePath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await using var destStream = System.IO.File.Create(newPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await sourceStream.CopyToAsync(destStream, cancellationToken).ConfigureAwait(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+356
to
+364
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var targetFile = new FileInfo(newPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetFile.Attributes = sourceFile.Attributes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (preserveTimestamps) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetFile.CreationTimeUtc = sourceFile.CreationTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetFile.LastWriteTimeUtc = sourceFile.LastWriteTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetFile.LastAccessTimeUtc = sourceFile.LastAccessTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+348
to
+375
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Preserve root directory attributes and timestamps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var targetRootDir = new DirectoryInfo(targetPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetRootDir.Attributes = DirectoryInfo.Attributes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (preserveTimestamps) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetRootDir.CreationTimeUtc = DirectoryInfo.CreationTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetRootDir.LastWriteTimeUtc = DirectoryInfo.LastWriteTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetRootDir.LastAccessTimeUtc = DirectoryInfo.LastAccessTimeUtc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Folder(targetPath); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+306
to
+389
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Folder MoveTo(string path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogFolderOperationWithDestination("Moving Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -278,6 +396,26 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Asynchronously moves the folder to a new path. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Uses thread pool offloading as no native async move API exists in .NET. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="path">The destination path.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Cancellation token.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>This folder instance for method chaining.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Task<Folder> MoveToAsync(string path, CancellationToken cancellationToken = default) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LogFolderOperationWithDestination("Moving Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Task.Run(() => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DirectoryInfo.MoveTo(path); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+405
to
+414
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+403
to
+416
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Uses thread pool offloading as no native async move API exists in .NET. | |
| /// </remarks> | |
| /// <param name="path">The destination path.</param> | |
| /// <param name="cancellationToken">Cancellation token.</param> | |
| /// <returns>This folder instance for method chaining.</returns> | |
| public Task<Folder> MoveToAsync(string path, CancellationToken cancellationToken = default) | |
| { | |
| LogFolderOperationWithDestination("Moving Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); | |
| return Task.Run(() => | |
| { | |
| DirectoryInfo.MoveTo(path); | |
| return this; | |
| }, cancellationToken); | |
| /// Performs a synchronous move operation and returns a completed task for API consistency. | |
| /// </remarks> | |
| /// <param name="path">The destination path.</param> | |
| /// <param name="cancellationToken">Cancellation token.</param> | |
| /// <returns>This folder instance for method chaining.</returns> | |
| public Task<Folder> MoveToAsync(string path, CancellationToken cancellationToken = default) | |
| { | |
| cancellationToken.ThrowIfCancellationRequested(); | |
| LogFolderOperationWithDestination("Moving Folder: {Source} > {Destination} [Module: {ModuleName}, Activity: {ActivityId}]", this, path); | |
| DirectoryInfo.MoveTo(path); | |
| return Task.FromResult(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new CreateAsync method lacks test coverage. Since the synchronous Create method has comprehensive test coverage in FileTests.cs, the async version should have corresponding tests to verify it works correctly and handles edge cases like creating files in non-existent directories.