Skip to content

Commit 8e6930a

Browse files
Rename files sample: .NET 10, batch rename name collision fix, folder and extension renames (#2355)
Retarget to net10.0 and current xUnit packages, make the sample runnable with Program.cs, check the new name is free before each batch move, add Directory.Move and Path.ChangeExtension helpers, and add five tests.
1 parent 6152fb5 commit 8e6930a

7 files changed

Lines changed: 174 additions & 14 deletions

File tree

‎files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/FileRenameService.cs‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,13 @@ public static void ExecuteRenameFileWithFileClass()
2222

2323
Directory.Delete(directoryPath, true);
2424
}
25-
}
25+
26+
public static string ChangeExtension(string filePath, string newExtension)
27+
{
28+
var newFilePath = Path.ChangeExtension(filePath, newExtension);
29+
30+
File.Move(filePath, newFilePath);
31+
32+
return newFilePath;
33+
}
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace RenameFilesWithCSharp;
2+
3+
public static class FolderRenameService
4+
{
5+
public static void RenameFolder(string oldFolder, string newFolder)
6+
{
7+
Directory.Move(oldFolder, newFolder);
8+
}
9+
10+
public static void ExecuteRenameFolder()
11+
{
12+
var parentPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
13+
14+
var oldFolder = Path.Combine(parentPath, "Invoices2025");
15+
Directory.CreateDirectory(Path.Combine(oldFolder, "Q1"));
16+
File.Create(Path.Combine(oldFolder, "Q1", "invoice-001.pdf")).Dispose();
17+
18+
var newFolder = Path.Combine(parentPath, "Invoices-2025");
19+
20+
RenameFolder(oldFolder, newFolder);
21+
22+
Console.WriteLine($"Renamed {Path.GetFileName(oldFolder)} to {Path.GetFileName(newFolder)}");
23+
Console.WriteLine($"Invoice moved along: {File.Exists(Path.Combine(newFolder, "Q1", "invoice-001.pdf"))}");
24+
25+
Directory.Delete(parentPath, true);
26+
}
27+
}

‎files-csharp/RenameFilesWithCSharp/RenameFilesWithCSharp/ImageRenameService.cs‎

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,45 @@ public static void RenameImagesWithDateTime(string directoryPath)
1616

1717
var fileExtension = Path.GetExtension(imageFile);
1818

19-
var newFileName = $"Image_{creationTime:yyyy-MM-dd_HHmmss}{fileExtension}";
19+
var baseName = $"Image_{creationTime:yyyy-MM-dd_HHmmss}";
2020

21-
var newFilePath = Path.Combine(directoryPath, newFileName);
21+
var newFilePath = Path.Combine(directoryPath, $"{baseName}{fileExtension}");
22+
23+
for (var copy = 1; File.Exists(newFilePath) && newFilePath != imageFile; copy++)
24+
{
25+
newFilePath = Path.Combine(directoryPath, $"{baseName}_{copy}{fileExtension}");
26+
}
2227

2328
File.Move(imageFile, newFilePath);
2429
}
2530
}
26-
}
31+
32+
public static void RenameImagesWithDateTimeUsingFileInfo(string directoryPath)
33+
{
34+
var allowedExtensions = new[] { ".jpg", ".png", ".jpeg", ".gif" };
35+
36+
var imageFiles = Directory.EnumerateFiles(directoryPath)
37+
.Where(file => allowedExtensions.Contains(Path.GetExtension(file),
38+
StringComparer.OrdinalIgnoreCase));
39+
40+
foreach (var imageFile in imageFiles)
41+
{
42+
var file = new FileInfo(imageFile);
43+
44+
var creationTime = file.CreationTime;
45+
46+
var fileExtension = file.Extension;
47+
48+
var baseName = $"Image_{creationTime:yyyy-MM-dd_HHmmss}";
49+
50+
var newFilePath = Path.Combine(directoryPath, $"{baseName}{fileExtension}");
51+
52+
for (var copy = 1; File.Exists(newFilePath) && newFilePath != imageFile; copy++)
53+
{
54+
newFilePath = Path.Combine(directoryPath, $"{baseName}_{copy}{fileExtension}");
55+
}
56+
57+
file.MoveTo(newFilePath);
58+
}
59+
}
60+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using RenameFilesWithCSharp;
2+
3+
FileFinderService.ExecuteFindFilesInFolder();
4+
FileRenameService.ExecuteRenameFileWithFileClass();
5+
FileInfoRenameService.ExecuteRenameFileWithFileInfoClass();
6+
FolderRenameService.ExecuteRenameFolder();
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<OutputType>Library</OutputType>
4-
<TargetFramework>net8.0</TargetFramework>
3+
<OutputType>Exe</OutputType>
4+
<TargetFramework>net10.0</TargetFramework>
55
<ImplicitUsings>enable</ImplicitUsings>
66
<Nullable>enable</Nullable>
77
</PropertyGroup>
8-
</Project>
8+
</Project>

‎files-csharp/RenameFilesWithCSharp/Tests/RenameFilesUnitTests.cs‎

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,88 @@ public void WhenBatchRenamingPhotosWithDateTimeAndNoAllowedExtensionsExist_ThenN
9797
// Assert
9898
Assert.Equal(existingFiles, updatedFiles);
9999
}
100-
}
100+
101+
[Fact]
102+
public void WhenTwoPhotosShareACreationSecond_ThenBothAreRenamedWithoutCollision()
103+
{
104+
var first = Path.Combine(_tempDirectory, "a.jpg");
105+
var second = Path.Combine(_tempDirectory, "b.jpg");
106+
File.Create(first).Close();
107+
File.Create(second).Close();
108+
var sameSecond = new DateTime(2025, 6, 1, 10, 30, 0);
109+
File.SetCreationTime(first, sameSecond);
110+
File.SetCreationTime(second, sameSecond);
111+
112+
// Act
113+
ImageRenameService.RenameImagesWithDateTime(_tempDirectory);
114+
115+
// Assert
116+
var names = Directory.GetFiles(_tempDirectory).Select(f => Path.GetFileName(f)).Order(StringComparer.Ordinal).ToArray();
117+
Assert.Equal(["Image_2025-06-01_103000.jpg", "Image_2025-06-01_103000_1.jpg"], names);
118+
}
119+
120+
[Fact]
121+
public void WhenBatchRenameRunsTwice_ThenSecondRunChangesNothing()
122+
{
123+
var sameSecond = new DateTime(2025, 6, 1, 10, 30, 0);
124+
foreach (var name in new[] { "a.jpg", "b.jpg" })
125+
{
126+
var path = Path.Combine(_tempDirectory, name);
127+
File.Create(path).Close();
128+
File.SetCreationTime(path, sameSecond);
129+
}
130+
ImageRenameService.RenameImagesWithDateTimeUsingFileInfo(_tempDirectory);
131+
var afterFirstRun = Directory.GetFiles(_tempDirectory).Order().ToArray();
132+
133+
// Act
134+
ImageRenameService.RenameImagesWithDateTimeUsingFileInfo(_tempDirectory);
135+
136+
// Assert
137+
Assert.Equal(afterFirstRun, Directory.GetFiles(_tempDirectory).Order().ToArray());
138+
}
139+
140+
[Fact]
141+
public void GivenExistingFolder_WhenRenamingFolder_ThenContentMovesWithIt()
142+
{
143+
var oldFolder = Path.Combine(_tempDirectory, "Invoices2025");
144+
Directory.CreateDirectory(oldFolder);
145+
File.WriteAllText(Path.Combine(oldFolder, "invoice-001.pdf"), "content");
146+
var newFolder = Path.Combine(_tempDirectory, "Invoices-2025");
147+
148+
// Act
149+
FolderRenameService.RenameFolder(oldFolder, newFolder);
150+
151+
// Assert
152+
Assert.False(Directory.Exists(oldFolder));
153+
Assert.True(File.Exists(Path.Combine(newFolder, "invoice-001.pdf")));
154+
}
155+
156+
[Fact]
157+
public void GivenTextFile_WhenChangingExtension_ThenOnlyExtensionChanges()
158+
{
159+
var filePath = Path.Combine(_tempDirectory, "notes.txt");
160+
File.WriteAllText(filePath, "content");
161+
162+
// Act
163+
var newFilePath = FileRenameService.ChangeExtension(filePath, ".md");
164+
165+
// Assert
166+
Assert.Equal(Path.Combine(_tempDirectory, "notes.md"), newFilePath);
167+
Assert.True(File.Exists(newFilePath));
168+
Assert.False(File.Exists(filePath));
169+
}
170+
171+
[Fact]
172+
public void GivenExistingDestination_WhenMovingWithoutOverwrite_ThenIOException()
173+
{
174+
var source = Path.Combine(_tempDirectory, "a.txt");
175+
var destination = Path.Combine(_tempDirectory, "b.txt");
176+
File.WriteAllText(source, "new");
177+
File.WriteAllText(destination, "old");
178+
179+
Assert.Throws<IOException>(() => File.Move(source, destination));
180+
181+
File.Move(source, destination, overwrite: true);
182+
Assert.Equal("new", File.ReadAllText(destination));
183+
}
184+
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net8.0</TargetFramework>
3+
<TargetFramework>net10.0</TargetFramework>
44
<ImplicitUsings>enable</ImplicitUsings>
55
<Nullable>enable</Nullable>
66
<IsPackable>false</IsPackable>
77
<IsTestProject>true</IsTestProject>
88
</PropertyGroup>
99
<ItemGroup>
10-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
11-
<PackageReference Include="xunit" Version="2.4.2" />
12-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
10+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
11+
<PackageReference Include="xunit" Version="2.9.3" />
12+
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
<PrivateAssets>all</PrivateAssets>
1515
</PackageReference>
16-
<PackageReference Include="coverlet.collector" Version="3.2.0">
16+
<PackageReference Include="coverlet.collector" Version="10.1.0">
1717
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1818
<PrivateAssets>all</PrivateAssets>
1919
</PackageReference>
2020
</ItemGroup>
2121
<ItemGroup>
2222
<ProjectReference Include="..\RenameFilesWithCSharp\RenameFilesWithCSharp.csproj" />
2323
</ItemGroup>
24-
</Project>
24+
</Project>

0 commit comments

Comments
 (0)