From 57bc371ad6da453e8843d5688623e7cf9080fa6f Mon Sep 17 00:00:00 2001 From: Florian Greinacher Date: Wed, 25 Nov 2020 23:30:13 +0100 Subject: [PATCH] feat: add overwrite option to File.Move and FileInfo.MoveTo --- Directory.Build.props | 1 + .../MockFile.cs | 46 +++++++++++++++++++ .../MockFileInfo.cs | 13 ++++++ src/System.IO.Abstractions/FileBase.cs | 5 ++ src/System.IO.Abstractions/FileInfoBase.cs | 5 ++ src/System.IO.Abstractions/FileInfoWrapper.cs | 7 +++ src/System.IO.Abstractions/FileWrapper.cs | 8 ++++ src/System.IO.Abstractions/IFile.cs | 6 ++- src/System.IO.Abstractions/IFileInfo.cs | 6 ++- .../MockFileInfoTests.cs | 21 +++++++++ .../MockFileLockTests.cs | 2 +- .../MockFileMoveTests.cs | 25 +++++++++- .../ApiParityTests.FileInfo_.NET 5.0.snap | 3 +- .../ApiParityTests.File_.NET 5.0.snap | 4 +- version.json | 2 +- 15 files changed, 143 insertions(+), 11 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index c1f77ebed..230e4f305 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,6 +12,7 @@ https://github.com/System-IO-Abstractions/System.IO.Abstractions MIT $(DefineConstants);FEATURE_ASYNC_FILE;FEATURE_ENUMERATION_OPTIONS;FEATURE_ADVANCED_PATH_OPERATIONSFEATURE_PATH_JOIN_WITH_SPAN + $(DefineConstants);FEATURE_FILE_MOVE_WITH_OVERWRITE diff --git a/src/System.IO.Abstractions.TestingHelpers/MockFile.cs b/src/System.IO.Abstractions.TestingHelpers/MockFile.cs index 3714dcb6e..4f571200b 100644 --- a/src/System.IO.Abstractions.TestingHelpers/MockFile.cs +++ b/src/System.IO.Abstractions.TestingHelpers/MockFile.cs @@ -357,6 +357,52 @@ public override void Move(string sourceFileName, string destFileName) mockFileDataAccessor.RemoveFile(sourceFileName); } +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + public override void Move(string sourceFileName, string destFileName, bool overwrite) + { + if (sourceFileName == null) + { + throw CommonExceptions.FilenameCannotBeNull(nameof(sourceFileName)); + } + + if (destFileName == null) + { + throw CommonExceptions.FilenameCannotBeNull(nameof(destFileName)); + } + + mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(sourceFileName, nameof(sourceFileName)); + mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(destFileName, nameof(destFileName)); + + if (mockFileDataAccessor.GetFile(destFileName) != null) + { + if (destFileName.Equals(sourceFileName)) + { + return; + } + else if (!overwrite) + { + throw new IOException("A file can not be created if it already exists."); + } + } + + + var sourceFile = mockFileDataAccessor.GetFile(sourceFileName); + + if (sourceFile == null) + { + throw CommonExceptions.FileNotFound(sourceFileName); + } + if (!sourceFile.AllowedFileShare.HasFlag(FileShare.Delete)) + { + throw CommonExceptions.ProcessCannotAccessFileInUse(); + } + VerifyDirectoryExists(destFileName); + + mockFileDataAccessor.AddFile(destFileName, new MockFileData(sourceFile)); + mockFileDataAccessor.RemoveFile(sourceFileName); + } +#endif + public override Stream Open(string path, FileMode mode) { mockFileDataAccessor.PathVerifier.IsLegalAbsoluteOrRelative(path, "path"); diff --git a/src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs b/src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs index c9a02abb7..a8d65362f 100644 --- a/src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs +++ b/src/System.IO.Abstractions.TestingHelpers/MockFileInfo.cs @@ -236,6 +236,19 @@ public override void MoveTo(string destFileName) path = movedFileInfo.FullName; } +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + public override void MoveTo(string destFileName, bool overwrite) + { + var movedFileInfo = CopyTo(destFileName, overwrite); + if (destFileName == FullName) + { + return; + } + Delete(); + path = movedFileInfo.FullName; + } +#endif + public override Stream Open(FileMode mode) { return new MockFile(mockFileSystem).Open(FullName, mode); diff --git a/src/System.IO.Abstractions/FileBase.cs b/src/System.IO.Abstractions/FileBase.cs index c653fc06a..40c0dd5c9 100644 --- a/src/System.IO.Abstractions/FileBase.cs +++ b/src/System.IO.Abstractions/FileBase.cs @@ -260,6 +260,11 @@ protected FileBase(IFileSystem fileSystem) /// public abstract void Move(string sourceFileName, string destFileName); +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + /// + public abstract void Move(string sourceFileName, string destFileName, bool overwrite); +#endif + /// public abstract Stream Open(string path, FileMode mode); diff --git a/src/System.IO.Abstractions/FileInfoBase.cs b/src/System.IO.Abstractions/FileInfoBase.cs index 34f1ffe2a..ca701942b 100644 --- a/src/System.IO.Abstractions/FileInfoBase.cs +++ b/src/System.IO.Abstractions/FileInfoBase.cs @@ -43,6 +43,11 @@ protected FileInfoBase(IFileSystem fileSystem) : base(fileSystem) /// public abstract void MoveTo(string destFileName); +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + /// + public abstract void MoveTo(string destFileName, bool overwrite); +#endif + /// public abstract Stream Open(FileMode mode); diff --git a/src/System.IO.Abstractions/FileInfoWrapper.cs b/src/System.IO.Abstractions/FileInfoWrapper.cs index aec489d97..2c23dc5a3 100644 --- a/src/System.IO.Abstractions/FileInfoWrapper.cs +++ b/src/System.IO.Abstractions/FileInfoWrapper.cs @@ -134,6 +134,13 @@ public override void MoveTo(string destFileName) instance.MoveTo(destFileName); } +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + public override void MoveTo(string destFileName, bool overwrite) + { + instance.MoveTo(destFileName, overwrite); + } +#endif + public override Stream Open(FileMode mode) { return instance.Open(mode); diff --git a/src/System.IO.Abstractions/FileWrapper.cs b/src/System.IO.Abstractions/FileWrapper.cs index b4baa2e27..1d41f34cb 100644 --- a/src/System.IO.Abstractions/FileWrapper.cs +++ b/src/System.IO.Abstractions/FileWrapper.cs @@ -149,6 +149,14 @@ public override void Move(string sourceFileName, string destFileName) File.Move(sourceFileName, destFileName); } +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + public override void Move(string sourceFileName, string destFileName, bool overwrite) + { + File.Move(sourceFileName, destFileName, overwrite); + } +#endif + + public override Stream Open(string path, FileMode mode) { return File.Open(path, mode); diff --git a/src/System.IO.Abstractions/IFile.cs b/src/System.IO.Abstractions/IFile.cs index e641a0ef5..dfa45c14f 100644 --- a/src/System.IO.Abstractions/IFile.cs +++ b/src/System.IO.Abstractions/IFile.cs @@ -61,8 +61,12 @@ public partial interface IFile DateTime GetLastWriteTime(string path); /// DateTime GetLastWriteTimeUtc(string path); - /// + /// void Move(string sourceFileName, string destFileName); +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + /// + void Move(string sourceFileName, string destFileName, bool overwrite); +#endif /// Stream Open(string path, FileMode mode); /// diff --git a/src/System.IO.Abstractions/IFileInfo.cs b/src/System.IO.Abstractions/IFileInfo.cs index 970d6d2d7..7b77c89f0 100644 --- a/src/System.IO.Abstractions/IFileInfo.cs +++ b/src/System.IO.Abstractions/IFileInfo.cs @@ -22,8 +22,12 @@ public interface IFileInfo : IFileSystemInfo FileSecurity GetAccessControl(); /// FileSecurity GetAccessControl(AccessControlSections includeSections); - /// + /// void MoveTo(string destFileName); +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + /// + void MoveTo(string destFileName, bool overwrite); +#endif /// Stream Open(FileMode mode); /// diff --git a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs index 50b793f8e..4065a5035 100644 --- a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs +++ b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileInfoTests.cs @@ -527,6 +527,27 @@ public void MockFileInfo_MoveTo_ThrowsExceptionIfSourceDoesntExist() Assert.Throws(action); } + + +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + [Test] + public void MockFileInfo_MoveToWithOverwrite_ShouldSucceedWhenTargetAlreadyExists() + { + string sourceFilePath = XFS.Path(@"c:\something\demo.txt"); + string sourceFileContent = "this is some content"; + string destFilePath = XFS.Path(@"c:\somethingelse\demo1.txt"); + var fileSystem = new MockFileSystem(new Dictionary + { + {sourceFilePath, new MockFileData(sourceFileContent)}, + {destFilePath, new MockFileData(sourceFileContent)} + }); + + fileSystem.FileInfo.FromFileName(sourceFilePath).MoveTo(destFilePath, overwrite: true); + + Assert.That(fileSystem.File.ReadAllText(destFilePath), Is.EqualTo(sourceFileContent)); + } +#endif + [Test] public void MockFileInfo_CopyTo_ThrowsExceptionIfSourceDoesntExist() { diff --git a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileLockTests.cs b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileLockTests.cs index bc0a3d293..fdc3bb908 100644 --- a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileLockTests.cs +++ b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileLockTests.cs @@ -186,6 +186,6 @@ public void MockFile_Lock_FileShareDeleteDoesNotThrowDelete() Assert.DoesNotThrow(() => filesystem.File.Delete(filepath)); } - private static IResolveConstraint IOException() => Is.TypeOf().And.Property("HResult").EqualTo(unchecked((int) 0x80070020)); + private static IResolveConstraint IOException() => Is.TypeOf().And.Property("HResult").EqualTo(unchecked((int)0x80070020)); } } diff --git a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileMoveTests.cs b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileMoveTests.cs index bcbb37d7b..1dc14c757 100644 --- a/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileMoveTests.cs +++ b/tests/System.IO.Abstractions.TestingHelpers.Tests/MockFileMoveTests.cs @@ -63,6 +63,25 @@ public void MockFile_Move_ShouldThrowIOExceptionWhenTargetAlreadyExists() Assert.That(exception.Message, Is.EqualTo("A file can not be created if it already exists.")); } +#if FEATURE_FILE_MOVE_WITH_OVERWRITE + [Test] + public void MockFile_MoveWithOverwrite_ShouldSucceedWhenTargetAlreadyExists() + { + string sourceFilePath = XFS.Path(@"c:\something\demo.txt"); + string sourceFileContent = "this is some content"; + string destFilePath = XFS.Path(@"c:\somethingelse\demo1.txt"); + var fileSystem = new MockFileSystem(new Dictionary + { + {sourceFilePath, new MockFileData(sourceFileContent)}, + {destFilePath, new MockFileData(sourceFileContent)} + }); + + fileSystem.File.Move(sourceFilePath, destFilePath, overwrite: true); + + Assert.That(fileSystem.File.ReadAllText(destFilePath), Is.EqualTo(sourceFileContent)); + } +#endif + [Test] public void MockFile_Move_ShouldThrowArgumentNullExceptionWhenSourceIsNull_Message() { @@ -227,7 +246,8 @@ public void MockFile_Move_ShouldThrowArgumentExceptionWhenSourceIsEmpty_Message( } [Test] - public void MockFile_Move_ShouldThrowArgumentExceptionWhenSourceIsEmpty_ParamName() { + public void MockFile_Move_ShouldThrowArgumentExceptionWhenSourceIsEmpty_ParamName() + { string destFilePath = XFS.Path(@"c:\something\demo.txt"); var fileSystem = new MockFileSystem(); @@ -260,7 +280,8 @@ public void MockFile_Move_ShouldThrowArgumentNullExceptionWhenTargetIsNull_Messa } [Test] - public void MockFile_Move_ShouldThrowArgumentNullExceptionWhenTargetIsNull_ParamName() { + public void MockFile_Move_ShouldThrowArgumentNullExceptionWhenTargetIsNull_ParamName() + { string sourceFilePath = XFS.Path(@"c:\something\demo.txt"); var fileSystem = new MockFileSystem(); diff --git a/tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.FileInfo_.NET 5.0.snap b/tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.FileInfo_.NET 5.0.snap index ae8e3379d..e2e2adfd7 100644 --- a/tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.FileInfo_.NET 5.0.snap +++ b/tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.FileInfo_.NET 5.0.snap @@ -8,7 +8,6 @@ "System.Object GetLifetimeService()", "System.Object InitializeLifetimeService()", "Void .ctor(System.String)", - "Void GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)", - "Void MoveTo(System.String, Boolean)" + "Void GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)" ] } diff --git a/tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.File_.NET 5.0.snap b/tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.File_.NET 5.0.snap index 1b942add5..fd4ac9b06 100644 --- a/tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.File_.NET 5.0.snap +++ b/tests/System.IO.Abstractions.Tests/__snapshots__/ApiParityTests.File_.NET 5.0.snap @@ -6,7 +6,5 @@ "System.Threading.Tasks.Task WriteAllLinesAsync(System.String, System.String[], System.Threading.CancellationToken)", "Void SetAccessControl(System.String, System.Security.AccessControl.FileSecurity)" ], - "MissingMembers": [ - "Void Move(System.String, System.String, Boolean)" - ] + "MissingMembers": [] } diff --git a/version.json b/version.json index 7788c99ce..6da463ddb 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.0", + "version": "13.1", "assemblyVersion": { "precision": "major" },