diff --git a/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/FileRenameService.cs b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/FileRenameService.cs index 92f0e29311..fae51aabad 100644 --- a/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/FileRenameService.cs +++ b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/FileRenameService.cs @@ -22,4 +22,13 @@ public static void ExecuteRenameFileWithFileClass() Directory.Delete(directoryPath, true); } -} \ No newline at end of file + + public static string ChangeExtension(string filePath, string newExtension) + { + var newFilePath = Path.ChangeExtension(filePath, newExtension); + + File.Move(filePath, newFilePath); + + return newFilePath; + } +} diff --git a/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/FolderRenameService.cs b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/FolderRenameService.cs new file mode 100644 index 0000000000..3a2f14d059 --- /dev/null +++ b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/FolderRenameService.cs @@ -0,0 +1,27 @@ +namespace RenameFilesWithCSharp; + +public static class FolderRenameService +{ + public static void RenameFolder(string oldFolder, string newFolder) + { + Directory.Move(oldFolder, newFolder); + } + + public static void ExecuteRenameFolder() + { + var parentPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + var oldFolder = Path.Combine(parentPath, "Invoices2025"); + Directory.CreateDirectory(Path.Combine(oldFolder, "Q1")); + File.Create(Path.Combine(oldFolder, "Q1", "invoice-001.pdf")).Dispose(); + + var newFolder = Path.Combine(parentPath, "Invoices-2025"); + + RenameFolder(oldFolder, newFolder); + + Console.WriteLine($"Renamed {Path.GetFileName(oldFolder)} to {Path.GetFileName(newFolder)}"); + Console.WriteLine($"Invoice moved along: {File.Exists(Path.Combine(newFolder, "Q1", "invoice-001.pdf"))}"); + + Directory.Delete(parentPath, true); + } +} diff --git a/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/ImageRenameService.cs b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/ImageRenameService.cs index 011ed4cae4..eb4bc021d3 100644 --- a/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/ImageRenameService.cs +++ b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/ImageRenameService.cs @@ -16,11 +16,45 @@ public static void RenameImagesWithDateTime(string directoryPath) var fileExtension = Path.GetExtension(imageFile); - var newFileName = $"Image_{creationTime:yyyy-MM-dd_HHmmss}{fileExtension}"; + var baseName = $"Image_{creationTime:yyyy-MM-dd_HHmmss}"; - var newFilePath = Path.Combine(directoryPath, newFileName); + var newFilePath = Path.Combine(directoryPath, $"{baseName}{fileExtension}"); + + for (var copy = 1; File.Exists(newFilePath) && newFilePath != imageFile; copy++) + { + newFilePath = Path.Combine(directoryPath, $"{baseName}_{copy}{fileExtension}"); + } File.Move(imageFile, newFilePath); } } -} \ No newline at end of file + + public static void RenameImagesWithDateTimeUsingFileInfo(string directoryPath) + { + var allowedExtensions = new[] { ".jpg", ".png", ".jpeg", ".gif" }; + + var imageFiles = Directory.EnumerateFiles(directoryPath) + .Where(file => allowedExtensions.Contains(Path.GetExtension(file), + StringComparer.OrdinalIgnoreCase)); + + foreach (var imageFile in imageFiles) + { + var file = new FileInfo(imageFile); + + var creationTime = file.CreationTime; + + var fileExtension = file.Extension; + + var baseName = $"Image_{creationTime:yyyy-MM-dd_HHmmss}"; + + var newFilePath = Path.Combine(directoryPath, $"{baseName}{fileExtension}"); + + for (var copy = 1; File.Exists(newFilePath) && newFilePath != imageFile; copy++) + { + newFilePath = Path.Combine(directoryPath, $"{baseName}_{copy}{fileExtension}"); + } + + file.MoveTo(newFilePath); + } + } +} diff --git a/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/Program.cs b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/Program.cs new file mode 100644 index 0000000000..6489332579 --- /dev/null +++ b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/Program.cs @@ -0,0 +1,6 @@ +using RenameFilesWithCSharp; + +FileFinderService.ExecuteFindFilesInFolder(); +FileRenameService.ExecuteRenameFileWithFileClass(); +FileInfoRenameService.ExecuteRenameFileWithFileInfoClass(); +FolderRenameService.ExecuteRenameFolder(); diff --git a/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/RenameFilesWithCSharp.csproj b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/RenameFilesWithCSharp.csproj index d81befcd75..238f157e2e 100644 --- a/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/RenameFilesWithCSharp.csproj +++ b/files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/RenameFilesWithCSharp.csproj @@ -1,8 +1,8 @@ - Library - net8.0 + Exe + net10.0 enable enable - \ No newline at end of file + diff --git a/files-csharp/RenameFilesWithCSharp/Tests/RenameFilesUnitTests.cs b/files-csharp/RenameFilesWithCSharp/Tests/RenameFilesUnitTests.cs index c26b181b76..a112084fb2 100644 --- a/files-csharp/RenameFilesWithCSharp/Tests/RenameFilesUnitTests.cs +++ b/files-csharp/RenameFilesWithCSharp/Tests/RenameFilesUnitTests.cs @@ -97,4 +97,88 @@ public void WhenBatchRenamingPhotosWithDateTimeAndNoAllowedExtensionsExist_ThenN // Assert Assert.Equal(existingFiles, updatedFiles); } -} \ No newline at end of file + + [Fact] + public void WhenTwoPhotosShareACreationSecond_ThenBothAreRenamedWithoutCollision() + { + var first = Path.Combine(_tempDirectory, "a.jpg"); + var second = Path.Combine(_tempDirectory, "b.jpg"); + File.Create(first).Close(); + File.Create(second).Close(); + var sameSecond = new DateTime(2025, 6, 1, 10, 30, 0); + File.SetCreationTime(first, sameSecond); + File.SetCreationTime(second, sameSecond); + + // Act + ImageRenameService.RenameImagesWithDateTime(_tempDirectory); + + // Assert + var names = Directory.GetFiles(_tempDirectory).Select(f => Path.GetFileName(f)).Order(StringComparer.Ordinal).ToArray(); + Assert.Equal(["Image_2025-06-01_103000.jpg", "Image_2025-06-01_103000_1.jpg"], names); + } + + [Fact] + public void WhenBatchRenameRunsTwice_ThenSecondRunChangesNothing() + { + var sameSecond = new DateTime(2025, 6, 1, 10, 30, 0); + foreach (var name in new[] { "a.jpg", "b.jpg" }) + { + var path = Path.Combine(_tempDirectory, name); + File.Create(path).Close(); + File.SetCreationTime(path, sameSecond); + } + ImageRenameService.RenameImagesWithDateTimeUsingFileInfo(_tempDirectory); + var afterFirstRun = Directory.GetFiles(_tempDirectory).Order().ToArray(); + + // Act + ImageRenameService.RenameImagesWithDateTimeUsingFileInfo(_tempDirectory); + + // Assert + Assert.Equal(afterFirstRun, Directory.GetFiles(_tempDirectory).Order().ToArray()); + } + + [Fact] + public void GivenExistingFolder_WhenRenamingFolder_ThenContentMovesWithIt() + { + var oldFolder = Path.Combine(_tempDirectory, "Invoices2025"); + Directory.CreateDirectory(oldFolder); + File.WriteAllText(Path.Combine(oldFolder, "invoice-001.pdf"), "content"); + var newFolder = Path.Combine(_tempDirectory, "Invoices-2025"); + + // Act + FolderRenameService.RenameFolder(oldFolder, newFolder); + + // Assert + Assert.False(Directory.Exists(oldFolder)); + Assert.True(File.Exists(Path.Combine(newFolder, "invoice-001.pdf"))); + } + + [Fact] + public void GivenTextFile_WhenChangingExtension_ThenOnlyExtensionChanges() + { + var filePath = Path.Combine(_tempDirectory, "notes.txt"); + File.WriteAllText(filePath, "content"); + + // Act + var newFilePath = FileRenameService.ChangeExtension(filePath, ".md"); + + // Assert + Assert.Equal(Path.Combine(_tempDirectory, "notes.md"), newFilePath); + Assert.True(File.Exists(newFilePath)); + Assert.False(File.Exists(filePath)); + } + + [Fact] + public void GivenExistingDestination_WhenMovingWithoutOverwrite_ThenIOException() + { + var source = Path.Combine(_tempDirectory, "a.txt"); + var destination = Path.Combine(_tempDirectory, "b.txt"); + File.WriteAllText(source, "new"); + File.WriteAllText(destination, "old"); + + Assert.Throws(() => File.Move(source, destination)); + + File.Move(source, destination, overwrite: true); + Assert.Equal("new", File.ReadAllText(destination)); + } +} diff --git a/files-csharp/RenameFilesWithCSharp/Tests/Tests.csproj b/files-csharp/RenameFilesWithCSharp/Tests/Tests.csproj index ae5d67b16c..d3138e175e 100644 --- a/files-csharp/RenameFilesWithCSharp/Tests/Tests.csproj +++ b/files-csharp/RenameFilesWithCSharp/Tests/Tests.csproj @@ -1,19 +1,19 @@ - net8.0 + net10.0 enable enable false true - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -21,4 +21,4 @@ - \ No newline at end of file +