Skip to content

Commit fc55ca2

Browse files
authored
Merge branch 'main' into fix/systemio/folder/invalid-path-in-filenotfoundexception
2 parents 3c14c98 + 37a0cc6 commit fc55ca2

File tree

8 files changed

+274
-120
lines changed

8 files changed

+274
-120
lines changed

src/Extensions/CopyAndMoveExtensions.cs

Lines changed: 0 additions & 106 deletions
This file was deleted.

src/Extensions/CopyExtensions.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.IO;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace OwlCore.Storage;
6+
7+
/// <summary>
8+
/// Extension methods for <see cref="IModifiableFolder"/>.
9+
/// </summary>
10+
public static partial class ModifiableFolderExtensions
11+
{
12+
/// <summary>
13+
/// Creates a copy of the provided file within this folder.
14+
/// </summary>
15+
/// <param name="destinationFolder">The folder where the copy is created.</param>
16+
/// <param name="fileToCopy">The file to be copied into this folder.</param>
17+
/// <param name="overwrite"><code>true</code> if any existing destination file can be overwritten; otherwise, <c>false</c>.</param>
18+
/// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
19+
/// <exception cref="FileAlreadyExistsException">Thrown when <paramref name="overwrite"/> is false and the resource being created already exists.</exception>
20+
public static async Task<IChildFile> CreateCopyOfAsync(this IModifiableFolder destinationFolder, IFile fileToCopy, bool overwrite, CancellationToken cancellationToken = default)
21+
{
22+
cancellationToken.ThrowIfCancellationRequested();
23+
24+
// If the destination folder declares a non-fallback copy path, try that.
25+
// Provide fallback in case this file is not a handled type.
26+
if (destinationFolder is ICreateCopyOf fastPath)
27+
return await fastPath.CreateCopyOfAsync(fileToCopy, overwrite, cancellationToken, fallback: CreateCopyOfFallbackAsync);
28+
29+
// Manual copy. Slower, but covers all scenarios.
30+
return await CreateCopyOfFallbackAsync(destinationFolder, fileToCopy, overwrite, cancellationToken);
31+
}
32+
33+
/// <summary>
34+
/// Creates a copy of the provided file within this folder.
35+
/// </summary>
36+
/// <param name="destinationFolder">The folder where the copy is created.</param>
37+
/// <param name="fileToCopy">The file to be copied into this folder.</param>
38+
/// <param name="newName">The name to use for the created file.</param>
39+
/// <param name="overwrite"><code>true</code> if any existing destination file can be overwritten; otherwise, <c>false</c>.</param>
40+
/// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
41+
/// <exception cref="FileAlreadyExistsException">Thrown when <paramref name="overwrite"/> is false and the resource being created already exists.</exception>
42+
public static async Task<IChildFile> CreateCopyOfAsync(this IModifiableFolder destinationFolder, IFile fileToCopy, bool overwrite, string newName, CancellationToken cancellationToken = default)
43+
{
44+
cancellationToken.ThrowIfCancellationRequested();
45+
46+
// If the destination folder declares a non-fallback copy path, try that.
47+
// Provide fallback in case this file is not a handled type.
48+
if (destinationFolder is ICreateRenamedCopyOf fastPath)
49+
return await fastPath.CreateCopyOfAsync(fileToCopy, overwrite, newName, cancellationToken, fallback: CreateCopyOfFallbackAsync);
50+
51+
// Manual copy. Slower, but covers all scenarios.
52+
return await CreateCopyOfFallbackAsync(destinationFolder, fileToCopy, overwrite, newName, cancellationToken);
53+
}
54+
55+
private static Task<IChildFile> CreateCopyOfFallbackAsync(IModifiableFolder destinationFolder, IFile fileToCopy, bool overwrite, CancellationToken cancellationToken = default)
56+
=> CreateCopyOfFallbackAsync(destinationFolder, fileToCopy, overwrite, fileToCopy.Name, cancellationToken);
57+
58+
private static async Task<IChildFile> CreateCopyOfFallbackAsync(IModifiableFolder destinationFolder, IFile fileToCopy, bool overwrite, string newName, CancellationToken cancellationToken = default)
59+
{
60+
cancellationToken.ThrowIfCancellationRequested();
61+
62+
// If the destination file exists and overwrite is false, it shouldn't be overwritten or returned as-is. Throw an exception instead.
63+
if (!overwrite)
64+
{
65+
try
66+
{
67+
var existing = await destinationFolder.GetFirstByNameAsync(newName, cancellationToken);
68+
if (existing is not null)
69+
throw new FileAlreadyExistsException(newName);
70+
}
71+
catch (FileNotFoundException) { }
72+
}
73+
74+
// Create the destination file.
75+
// 'overwrite: false' would have thrown above if the file exists, so either overwrite is already true or the file doesn't exist yet.
76+
// Always overwrite here so the file is empty.
77+
var newFile = await destinationFolder.CreateFileAsync(newName, overwrite: true, cancellationToken);
78+
using var destinationStream = await newFile.OpenStreamAsync(FileAccess.Write, cancellationToken: cancellationToken);
79+
80+
// Open the source file
81+
using var sourceStream = await fileToCopy.OpenStreamAsync(FileAccess.Read, cancellationToken: cancellationToken);
82+
83+
// Copy the src into the dest file
84+
await sourceStream.CopyToAsync(destinationStream, bufferSize: 81920, cancellationToken);
85+
86+
return newFile;
87+
}
88+
}

src/Extensions/ICreateCopyOf.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace OwlCore.Storage;
66

77
/// <summary>
8-
/// Provides a way for implementations to override behavior of the <see cref="ModifiableFolderExtensions.CreateCopyOfAsync"/> extension method.
8+
/// Provides a way for implementations to override behavior of the <see cref="ModifiableFolderExtensions.CreateCopyOfAsync(IModifiableFolder, IFile, bool, CancellationToken)"/> extension method.
99
/// </summary>
1010
/// <exception cref="FileNotFoundException">The item was not found in the provided folder.</exception>
1111
public interface ICreateCopyOf : IModifiableFolder
@@ -26,3 +26,26 @@ public interface ICreateCopyOf : IModifiableFolder
2626
/// </summary>
2727
/// <returns></returns>
2828
public delegate Task<IChildFile> CreateCopyOfDelegate(IModifiableFolder destination, IFile fileToCopy, bool overwrite, CancellationToken cancellationToken);
29+
30+
/// <summary>
31+
/// Provides a way for implementations to override behavior of the <see cref="ModifiableFolderExtensions.CreateCopyOfAsync(IModifiableFolder, IFile, bool, string, CancellationToken)"/> extension method.
32+
/// </summary>
33+
public interface ICreateRenamedCopyOf : ICreateCopyOf
34+
{
35+
/// <summary>
36+
/// Creates a copy of the provided file within this folder.
37+
/// </summary>
38+
/// <param name="fileToCopy">The file to be copied into this folder.</param>
39+
/// <param name="overwrite">If there is an existing destination file, <c>true</c> will overwrite it; otherwise <c>false</c> and the existing file is opened.</param>
40+
/// <param name="newName">The new name of the created file.</param>
41+
/// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
42+
/// <param name="fallback">The fallback to use if the provided <paramref name="fileToCopy"/> isn't supported.</param>
43+
/// <returns>The newly created (or opened if existing) file.</returns>
44+
Task<IChildFile> CreateCopyOfAsync(IFile fileToCopy, bool overwrite, string newName, CancellationToken cancellationToken, CreateRenamedCopyOfDelegate fallback);
45+
}
46+
47+
/// <summary>
48+
/// A delegate that provides a fallback for the <see cref="IMoveFrom.MoveFromAsync"/> method.
49+
/// </summary>
50+
/// <returns></returns>
51+
public delegate Task<IChildFile> CreateRenamedCopyOfDelegate(IModifiableFolder destination, IFile fileToCopy, bool overwrite, string newName, CancellationToken cancellationToken);

src/Extensions/IMoveFrom.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
using System;
2-
using System.IO;
1+
using System.IO;
32
using System.Threading;
43
using System.Threading.Tasks;
54

65
namespace OwlCore.Storage;
76

87
/// <summary>
9-
/// Provides a fast-path for the <see cref="ModifiableFolderExtensions.MoveFromAsync"/> extension method.
8+
/// Provides a fast-path for the <see cref="ModifiableFolderExtensions.CreateCopyOfAsync(IModifiableFolder, IFile, bool, CancellationToken)"/> extension method.
109
/// </summary>
1110
/// <exception cref="FileNotFoundException">The item was not found in the provided folder.</exception>
1211
public interface IMoveFrom : IModifiableFolder
@@ -26,4 +25,31 @@ public interface IMoveFrom : IModifiableFolder
2625
/// <summary>
2726
/// A delegate that provides a fallback for the <see cref="IMoveFrom.MoveFromAsync"/> method.
2827
/// </summary>
29-
public delegate Task<IChildFile> MoveFromDelegate(IModifiableFolder modifiableFolder, IChildFile file, IModifiableFolder source, bool overwrite, CancellationToken cancellationToken);
28+
public delegate Task<IChildFile> MoveFromDelegate(IModifiableFolder modifiableFolder, IChildFile file, IModifiableFolder source, bool overwrite, CancellationToken cancellationToken);
29+
30+
/// <summary>
31+
/// Provides a fast-path for the <see cref="ModifiableFolderExtensions.CreateCopyOfAsync(IModifiableFolder, IFile, bool, string, CancellationToken)"/> extension method.
32+
/// </summary>
33+
/// <remarks>
34+
/// This interface derives from <see cref="IMoveFrom"/> to enforce non-fallback compatibility with the non-rename overload.
35+
/// </remarks>
36+
/// <exception cref="FileNotFoundException">The item was not found in the provided folder.</exception>
37+
public interface IMoveRenamedFrom : IMoveFrom
38+
{
39+
/// <summary>
40+
/// Moves a storable item out of the provided folder, and into this folder. Returns the new item that resides in this folder.
41+
/// </summary>
42+
/// <param name="fileToMove">The file being moved into this folder.</param>
43+
/// <param name="source">The folder that <paramref name="fileToMove"/> is being moved from.</param>
44+
/// <param name="overwrite">If there is an existing destination file, <c>true</c> will overwrite it; otherwise <c>false</c> and the existing file is opened.</param>
45+
/// <param name="newName">The new name of the created file.</param>
46+
/// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
47+
/// <param name="fallback">The fallback to use if the provided <paramref name="fileToMove"/> isn't supported.</param>
48+
/// <returns>The newly created (or opened if existing) file.</returns>
49+
Task<IChildFile> MoveFromAsync(IChildFile fileToMove, IModifiableFolder source, bool overwrite, string newName, CancellationToken cancellationToken, MoveRenamedFromDelegate fallback);
50+
}
51+
52+
/// <summary>
53+
/// A delegate that provides a fallback for the <see cref="IMoveFrom.MoveFromAsync"/> method.
54+
/// </summary>
55+
public delegate Task<IChildFile> MoveRenamedFromDelegate(IModifiableFolder modifiableFolder, IChildFile file, IModifiableFolder source, bool overwrite, string newName, CancellationToken cancellationToken);

0 commit comments

Comments
 (0)