You can modify the function like this to keep the temp folder:
ConverterUtils.cs
public static void TryCleanupTempFolder(string tempFolderPath)
{
if (string.IsNullOrWhiteSpace(tempFolderPath)) return;
try
{
if (Directory.Exists(tempFolderPath))
{
Log.Debug($"Cleaning up temp folder contents '{tempFolderPath}'.");
// Delete all files in the folder
foreach (var file in Directory.GetFiles(tempFolderPath, "*", SearchOption.AllDirectories))
{
try
{
File.Delete(file);
}
catch (Exception fileEx)
{
Log.Warn($"Failed to delete file '{file}'): {fileEx.Message}");
}
}
// Delete all subdirectories
foreach (var dir in Directory.GetDirectories(tempFolderPath, "*", SearchOption.TopDirectoryOnly))
{
try
{
Directory.Delete(dir, recursive: true);
}
catch (Exception dirEx)
{
Log.Warn($"Failed to delete directory '{dir}': {dirEx.Message}");
}
}
Log.Info($"Temp folder contents cleaned: '{tempFolderPath}' (folder preserved).");
}
}
catch (Exception ex)
{
Log.Warn($"Failed to clean up temp folder '{tempFolderPath}': {ex.Message}");
}
}
Also add these tests to verify the behavior:
ConverterUtilsTests.cs
[Theory(DisplayName = "BuildOutputPath_UsesExtractedSourcePath_WhenProvided")]
[InlineData("ShapeFiles.zip", "iofr0c1nypl.shp", "GeoJson", "iofr0c1nypl.json")]
[InlineData("archive.tar.gz", "Cities.geojson", "Shapefile", "Cities.shp")]
[InlineData("MyData.7z", "Roads.kml", "GeoPackage", "Roads.gpkg")]
[InlineData("bundle.rar", "Points.csv", "Kml", "Points.kml")]
public void BuildOutputPath_UsesExtractedSourcePath_WhenProvided(
string archivePath,
string extractedPath,
string targetFormat,
string expectedFilename)
{
var outputFolder = Path.Combine(_root, "output");
Directory.CreateDirectory(outputFolder);
// When an extracted source path is provided, it should be used instead of the archive name
var result = ConverterUtils.BuildOutputPath(outputFolder, archivePath, targetFormat, extractedPath);
Assert.NotNull(result);
Assert.Equal(Path.Combine(outputFolder, expectedFilename), result);
}
[Theory(DisplayName = "BuildOutputPath_UsesOriginalPath_WhenExtractedSourceIsNull")]
[InlineData("Cities.geojson", null, "Shapefile", "Cities.shp")]
[InlineData("MyData.zip", null, "GeoJson", "MyData.json")]
public void BuildOutputPath_UsesOriginalPath_WhenExtractedSourceIsNull(
string inputPath,
string? extractedPath,
string targetFormat,
string expectedFilename)
{
var outputFolder = Path.Combine(_root, "output");
Directory.CreateDirectory(outputFolder);
// When no extracted source path is provided, should fall back to original input path
var result = ConverterUtils.BuildOutputPath(outputFolder, inputPath, targetFormat, extractedPath);
Assert.NotNull(result);
Assert.Equal(Path.Combine(outputFolder, expectedFilename), result);
}
[Fact(DisplayName = "BuildOutputPath_PreservesOriginalDataFileName_NotArchiveName")]
public void BuildOutputPath_PreservesOriginalDataFileName_NotArchiveName()
{
var outputFolder = Path.Combine(_root, "output");
Directory.CreateDirectory(outputFolder);
// Simulating real scenario: archive named "ShapeFiles.zip" contains "iofr0c1nypl.shp"
var archiveName = "ShapeFiles.zip";
var extractedFile = Path.Combine(_root, "temp", "iofr0c1nypl.shp");
// Without extracted path (incorrect - uses archive name)
var resultWithoutExtracted = ConverterUtils.BuildOutputPath(outputFolder, archiveName, "GeoJson");
Assert.NotNull(resultWithoutExtracted);
Assert.Equal("ShapeFiles.json", Path.GetFileName(resultWithoutExtracted));
// With extracted path (correct - uses actual data file name)
var resultWithExtracted = ConverterUtils.BuildOutputPath(outputFolder, archiveName, "GeoJson", extractedFile);
Assert.NotNull(resultWithExtracted);
Assert.Equal("iofr0c1nypl.json", Path.GetFileName(resultWithExtracted));
}
/// <summary>
/// Verifies that TryCleanupTempFolder removes folder contents but preserves the folder itself.
/// This ensures consistent behavior between single-file and archive processing.
/// </summary>
[Fact(DisplayName = "TryCleanupTempFolder_RemovesContentsButPreservesFolder")]
public void TryCleanupTempFolder_RemovesContentsButPreservesFolder()
{
var tempFolder = Path.Combine(_root, "cleanup_test");
Directory.CreateDirectory(tempFolder);
// Create files and subdirectories in temp folder
var file1 = Path.Combine(tempFolder, "file1.txt");
var file2 = Path.Combine(tempFolder, "file2.dat");
File.WriteAllText(file1, "content1");
File.WriteAllText(file2, "content2");
var subDir = Path.Combine(tempFolder, "subdir");
Directory.CreateDirectory(subDir);
var nestedFile = Path.Combine(subDir, "nested.txt");
File.WriteAllText(nestedFile, "nested content");
// Verify setup
Assert.True(Directory.Exists(tempFolder));
Assert.True(File.Exists(file1));
Assert.True(File.Exists(file2));
Assert.True(Directory.Exists(subDir));
Assert.True(File.Exists(nestedFile));
// Execute cleanup
ConverterUtils.TryCleanupTempFolder(tempFolder);
// Verify results: folder exists but is empty
Assert.True(Directory.Exists(tempFolder), "Temp folder should be preserved");
Assert.False(File.Exists(file1), "File1 should be deleted");
Assert.False(File.Exists(file2), "File2 should be deleted");
Assert.False(Directory.Exists(subDir), "Subdirectory should be deleted");
Assert.False(File.Exists(nestedFile), "Nested file should be deleted");
Assert.Empty(Directory.GetFiles(tempFolder, "*", SearchOption.AllDirectories));
Assert.Empty(Directory.GetDirectories(tempFolder, "*", SearchOption.AllDirectories));
}
/// <summary>
/// Verifies that TryCleanupTempFolder handles non-existent folders gracefully without throwing.
/// </summary>
[Fact(DisplayName = "TryCleanupTempFolder_HandlesNonExistentFolder")]
public void TryCleanupTempFolder_HandlesNonExistentFolder()
{
var nonExistentFolder = Path.Combine(_root, "does_not_exist");
// Should not throw
ConverterUtils.TryCleanupTempFolder(nonExistentFolder);
// Folder should still not exist
Assert.False(Directory.Exists(nonExistentFolder));
}
/// <summary>
/// Verifies that TryCleanupTempFolder handles null/empty/whitespace paths gracefully.
/// </summary>
[Theory(DisplayName = "TryCleanupTempFolder_HandlesInvalidPaths")]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("\t")]
public void TryCleanupTempFolder_HandlesInvalidPaths(string? invalidPath)
{
// Should not throw
ConverterUtils.TryCleanupTempFolder(invalidPath!);
}
/// <summary>
/// Verifies that TryCleanupTempFolder handles empty folders correctly (preserves the folder).
/// </summary>
[Fact(DisplayName = "TryCleanupTempFolder_HandlesEmptyFolder")]
public void TryCleanupTempFolder_HandlesEmptyFolder()
{
var emptyFolder = Path.Combine(_root, "empty_folder");
Directory.CreateDirectory(emptyFolder);
Assert.True(Directory.Exists(emptyFolder));
// Execute cleanup on empty folder
ConverterUtils.TryCleanupTempFolder(emptyFolder);
// Folder should still exist and remain empty
Assert.True(Directory.Exists(emptyFolder), "Empty folder should be preserved");
Assert.Empty(Directory.GetFiles(emptyFolder));
Assert.Empty(Directory.GetDirectories(emptyFolder));
}
/// <summary>
/// Verifies that TryCleanupTempFolder can be called multiple times idempotently.
/// </summary>
[Fact(DisplayName = "TryCleanupTempFolder_IsIdempotent")]
public void TryCleanupTempFolder_IsIdempotent()
{
var tempFolder = Path.Combine(_root, "idempotent_test");
Directory.CreateDirectory(tempFolder);
var file = Path.Combine(tempFolder, "test.txt");
File.WriteAllText(file, "content");
// First cleanup
ConverterUtils.TryCleanupTempFolder(tempFolder);
Assert.True(Directory.Exists(tempFolder));
Assert.False(File.Exists(file));
// Second cleanup on already-cleaned folder should not throw
ConverterUtils.TryCleanupTempFolder(tempFolder);
Assert.True(Directory.Exists(tempFolder));
// Third cleanup should also work
ConverterUtils.TryCleanupTempFolder(tempFolder);
Assert.True(Directory.Exists(tempFolder));
}
/// <summary>
/// Verifies that TryCleanupTempFolder handles deeply nested directory structures.
/// </summary>
[Fact(DisplayName = "TryCleanupTempFolder_HandlesDeeplyNestedStructures")]
public void TryCleanupTempFolder_HandlesDeeplyNestedStructures()
{
var tempFolder = Path.Combine(_root, "nested_test");
Directory.CreateDirectory(tempFolder);
// Create deeply nested structure
var level1 = Path.Combine(tempFolder, "level1");
var level2 = Path.Combine(level1, "level2");
var level3 = Path.Combine(level2, "level3");
Directory.CreateDirectory(level3);
var file1 = Path.Combine(level1, "file1.txt");
var file2 = Path.Combine(level2, "file2.txt");
var file3 = Path.Combine(level3, "file3.txt");
File.WriteAllText(file1, "level1");
File.WriteAllText(file2, "level2");
File.WriteAllText(file3, "level3");
// Execute cleanup
ConverterUtils.TryCleanupTempFolder(tempFolder);
// Verify all nested content is removed but folder is preserved
Assert.True(Directory.Exists(tempFolder), "Temp folder should be preserved");
Assert.False(Directory.Exists(level1), "Level1 directory should be deleted");
Assert.False(File.Exists(file1), "File1 should be deleted");
Assert.False(File.Exists(file2), "File2 should be deleted");
Assert.False(File.Exists(file3), "File3 should be deleted");
Assert.Empty(Directory.GetFileSystemEntries(tempFolder));
}