diff --git a/ImperatorToCK3/CommonUtils/FileOpeningHelper.cs b/ImperatorToCK3/CommonUtils/FileHelper.cs similarity index 66% rename from ImperatorToCK3/CommonUtils/FileOpeningHelper.cs rename to ImperatorToCK3/CommonUtils/FileHelper.cs index 05bdab895..f0c8e7d12 100644 --- a/ImperatorToCK3/CommonUtils/FileOpeningHelper.cs +++ b/ImperatorToCK3/CommonUtils/FileHelper.cs @@ -7,7 +7,7 @@ using Exceptions; using System.Text; -public static class FileOpeningHelper { +public static class FileHelper { private const string CloseProgramsHint = "You should close all programs that may be using the file."; private static bool IsFilesSharingViolation(Exception ex) { @@ -45,4 +45,27 @@ public static StreamWriter OpenWriteWithRetries(string filePath, Encoding encodi return writer; } + + public static void DeleteWithRetries(string filePath) { + const int maxAttempts = 10; + + int currentAttempt = 0; + + var policy = Policy + .Handle(IsFilesSharingViolation) + .WaitAndRetry(maxAttempts, + sleepDurationProvider: _ => TimeSpan.FromSeconds(30), + onRetry: (_, _, _) => { + currentAttempt++; + Logger.Warn($"Attempt {currentAttempt} to delete \"{filePath}\" failed."); + Logger.Warn(CloseProgramsHint); + }); + + try { + policy.Execute(() => File.Delete(filePath)); + } catch (IOException ex) when (IsFilesSharingViolation(ex)) { + Logger.Debug(ex.ToString()); + throw new UserErrorException($"Failed to delete \"{filePath}\". {CloseProgramsHint}"); + } + } } \ No newline at end of file diff --git a/ImperatorToCK3/Helpers/RakalyCaller.cs b/ImperatorToCK3/Helpers/RakalyCaller.cs index 6481ad62b..ac27c3b05 100644 --- a/ImperatorToCK3/Helpers/RakalyCaller.cs +++ b/ImperatorToCK3/Helpers/RakalyCaller.cs @@ -1,4 +1,5 @@ using commonItems; +using ImperatorToCK3.CommonUtils; using ImperatorToCK3.Exceptions; using System; using System.ComponentModel; @@ -140,7 +141,7 @@ public static void MeltSave(string savePath) { const string destFileName = "temp/melted_save.rome"; // first, delete target file if exists, as File.Move() does not support overwrite if (File.Exists(destFileName)) { - File.Delete(destFileName); + FileHelper.DeleteWithRetries(destFileName); } File.Move(meltedSaveName, destFileName); } diff --git a/ImperatorToCK3/Imperator/World.cs b/ImperatorToCK3/Imperator/World.cs index 9f771b5b8..ba80e8a1e 100644 --- a/ImperatorToCK3/Imperator/World.cs +++ b/ImperatorToCK3/Imperator/World.cs @@ -164,7 +164,7 @@ private void LaunchImperatorToExportCountryFlags(Configuration config) { string dataTypesLogPath = Path.Combine(config.ImperatorDocPath, "logs/data_types.log"); if (File.Exists(dataTypesLogPath)) { - File.Delete(dataTypesLogPath); + FileHelper.DeleteWithRetries(dataTypesLogPath); } Logger.Debug("Launching Imperator to extract coats of arms..."); @@ -218,7 +218,7 @@ private static EventHandler HandleImperatorProcessExit(Configuration config, Pro return (_, _) => { Logger.Debug($"Imperator process exited with code {imperatorProcess.ExitCode}. Removing temporary mod files..."); try { - File.Delete(Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod.mod")); + FileHelper.DeleteWithRetries(Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod.mod")); Directory.Delete(Path.Combine(config.ImperatorDocPath, "mod/coa_export_mod"), recursive: true); } catch (Exception e) { Logger.Warn($"Failed to remove temporary mod files: {e.Message}"); diff --git a/ImperatorToCK3/Outputter/BookmarkOutputter.cs b/ImperatorToCK3/Outputter/BookmarkOutputter.cs index 570a35ad3..cd753c5a4 100644 --- a/ImperatorToCK3/Outputter/BookmarkOutputter.cs +++ b/ImperatorToCK3/Outputter/BookmarkOutputter.cs @@ -56,7 +56,7 @@ public static async Task OutputBookmark(World world, Configuration config, CK3Lo sb.AppendLine("}"); var path = Path.Combine("output", config.OutputModName, "common/bookmarks/bookmarks/00_bookmarks.txt"); - await using var output = FileOpeningHelper.OpenWriteWithRetries(path, Encoding.UTF8); + await using var output = FileHelper.OpenWriteWithRetries(path, Encoding.UTF8); await output.WriteAsync(sb.ToString()); await DrawBookmarkMap(config, playerTitles, world); @@ -141,7 +141,7 @@ Configuration config private static async Task OutputBookmarkGroup(Configuration config) { var path = Path.Combine("output", config.OutputModName, "common/bookmarks/groups/00_bookmark_groups.txt"); - await using var output = FileOpeningHelper.OpenWriteWithRetries(path, Encoding.UTF8); + await using var output = FileHelper.OpenWriteWithRetries(path, Encoding.UTF8); await output.WriteLineAsync($"bm_converted = {{ default_start_date = {config.CK3BookmarkDate} }}"); } @@ -317,6 +317,6 @@ private static async Task ResaveImageAsDDS(string imagePath) { using (var magickImage = new MagickImage(imagePath)) { await magickImage.WriteAsync(Path.ChangeExtension(imagePath, ".dds")); } - File.Delete(imagePath); + FileHelper.DeleteWithRetries(imagePath); } } \ No newline at end of file diff --git a/ImperatorToCK3/Outputter/CharactersOutputter.cs b/ImperatorToCK3/Outputter/CharactersOutputter.cs index f332d9de2..1f37791ad 100644 --- a/ImperatorToCK3/Outputter/CharactersOutputter.cs +++ b/ImperatorToCK3/Outputter/CharactersOutputter.cs @@ -39,7 +39,7 @@ public static async Task OutputCharacters(string outputPath, CharacterCollection var sb = new StringBuilder(); var pathForCharactersFromIR = $"{outputPath}/history/characters/IRToCK3_fromImperator.txt"; - await using var charactersFromIROutput = FileOpeningHelper.OpenWriteWithRetries(pathForCharactersFromIR); + await using var charactersFromIROutput = FileHelper.OpenWriteWithRetries(pathForCharactersFromIR); foreach (var character in charactersFromIR) { CharacterOutputter.WriteCharacter(sb, character, conversionDate); await charactersFromIROutput.WriteAsync(sb.ToString()); @@ -47,7 +47,7 @@ public static async Task OutputCharacters(string outputPath, CharacterCollection } var pathForCharactersFromCK3 = $"{outputPath}/history/characters/IRToCK3_fromCK3.txt"; - await using var charactersFromCK3Output = FileOpeningHelper.OpenWriteWithRetries(pathForCharactersFromCK3, Encoding.UTF8); + await using var charactersFromCK3Output = FileHelper.OpenWriteWithRetries(pathForCharactersFromCK3, Encoding.UTF8); foreach (var character in charactersFromCK3) { CharacterOutputter.WriteCharacter(sb, character, conversionDate); await charactersFromCK3Output.WriteAsync(sb.ToString()); @@ -64,7 +64,7 @@ public static async Task BlankOutHistoricalPortraitModifiers(ModFilesystem ck3Mo if (ck3ModFS.GetActualFileLocation(modifiersFilePath) is not null) { string dummyPath = Path.Combine(outputPath, modifiersFilePath); - await using var output = FileOpeningHelper.OpenWriteWithRetries(dummyPath, Encoding.UTF8); + await using var output = FileHelper.OpenWriteWithRetries(dummyPath, Encoding.UTF8); await output.WriteLineAsync("# Dummy file to blank out historical portrait modifiers from CK3."); } } @@ -74,7 +74,7 @@ private static async Task OutputCharactersDNA(string outputPath, IEnumerable wars) } var path = Path.Combine(outputModPath, "history/wars/00_wars.txt"); - await using var output = FileOpeningHelper.OpenWriteWithRetries(path, Encoding.UTF8); + await using var output = FileHelper.OpenWriteWithRetries(path, Encoding.UTF8); await output.WriteAsync(sb.ToString()); Logger.IncrementProgress(); diff --git a/ImperatorToCK3/Outputter/WorldOutputter.cs b/ImperatorToCK3/Outputter/WorldOutputter.cs index 648678f3a..9be48da74 100644 --- a/ImperatorToCK3/Outputter/WorldOutputter.cs +++ b/ImperatorToCK3/Outputter/WorldOutputter.cs @@ -222,9 +222,9 @@ private static void OutputPlaysetInfo(World ck3World, string outputModName) { const string outFilePath = "playset_info.txt"; if (File.Exists(outFilePath)) { - File.Delete(outFilePath); + FileHelper.DeleteWithRetries(outFilePath); } - using var output = FileOpeningHelper.OpenWriteWithRetries(outFilePath, Encoding.UTF8); + using var output = FileHelper.OpenWriteWithRetries(outFilePath, Encoding.UTF8); foreach (var mod in modsForPlayset) { output.WriteLine($"{mod.Name.AddQuotes()}={mod.Path.AddQuotes()}");