Skip to content

Commit

Permalink
Handle file sharing violation when trying to write a file (#1728) #patch
Browse files Browse the repository at this point in the history
Related Sentry event ID: 3b8b48f6402d488484a0b3b86a6056bf
  • Loading branch information
IhateTrains authored Jan 25, 2024
1 parent 237e8af commit 0404f04
Show file tree
Hide file tree
Showing 16 changed files with 90 additions and 50 deletions.
50 changes: 50 additions & 0 deletions ImperatorToCK3/CommonUtils/FileOpeningHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace ImperatorToCK3.CommonUtils;

using commonItems;
using System;
using Polly;
using System.IO;
using Exceptions;
using System.Text;

public static class FileOpeningHelper {
private const string CloseProgramsHint = "You should close all programs that may be using the file.";

private static bool IsFilesSharingViolation(Exception ex) {
const int sharingViolationHResult = unchecked((int)0x80070020);
return ex.HResult == sharingViolationHResult;
}

public static StreamWriter OpenWriteWithRetries(string filePath) => OpenWriteWithRetries(filePath, Encoding.UTF8);

public static StreamWriter OpenWriteWithRetries(string filePath, Encoding encoding) {
const int maxAttempts = 10;
StreamWriter? writer = null;

int currentAttempt = 0;

var policy = Policy
.Handle<IOException>(IsFilesSharingViolation)
.WaitAndRetry(maxAttempts,
sleepDurationProvider: _ => TimeSpan.FromSeconds(30),
onRetry: (_, _, _) => {
currentAttempt++;
Logger.Warn($"Attempt {currentAttempt} to open \"{filePath}\" failed. {CloseProgramsHint}");
});

try {
policy.Execute(() => {
writer = new StreamWriter(filePath, append: false, encoding);
});
} catch (IOException ex) when (IsFilesSharingViolation(ex)) {
Logger.Debug(ex.ToString());
throw new UserErrorException($"Failed to open \"{filePath}\" for writing. {CloseProgramsHint}");
}

if (writer is null) {
throw new UserErrorException($"Failed to open \"{filePath}\" for writing: unknown error.");
}

return writer;
}
}
1 change: 1 addition & 0 deletions ImperatorToCK3/ImperatorToCK3.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
</PackageReference>
<PackageReference Include="PGCG.commonItems" Version="11.0.0" />
<PackageReference Include="PGCG.commonItems.SourceGenerators" Version="1.0.5" />
<PackageReference Include="Polly" Version="8.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.2" />
</ItemGroup>

Expand Down
10 changes: 4 additions & 6 deletions ImperatorToCK3/Outputter/BookmarkOutputter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using ImperatorToCK3.CK3;
using ImperatorToCK3.CK3.Map;
using ImperatorToCK3.CK3.Titles;
using ImperatorToCK3.CommonUtils;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
Expand All @@ -25,8 +26,7 @@ public static void OutputBookmark(World world, Configuration config) {
Logger.IncrementProgress();

var path = Path.Combine("output", config.OutputModName, "common/bookmarks/bookmarks/00_bookmarks.txt");
using var stream = File.OpenWrite(path);
using var output = new StreamWriter(stream, Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(path, Encoding.UTF8);

var provincePositions = world.MapData.ProvincePositions;

Expand Down Expand Up @@ -122,8 +122,7 @@ public static void OutputBookmark(World world, Configuration config) {

private static void OutputBookmarkGroup(Configuration config) {
var path = Path.Combine("output", config.OutputModName, "common/bookmarks/groups/00_bookmark_groups.txt");
using var stream = File.OpenWrite(path);
using var output = new StreamWriter(stream, Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(path, Encoding.UTF8);

output.WriteLine("bm_converted = {");
output.WriteLine($"\tdefault_start_date = {config.CK3BookmarkDate}");
Expand All @@ -135,8 +134,7 @@ private static void OutputBookmarkLoc(Configuration config, IDictionary<string,
var baseLocPath = Path.Combine("output", outputName, "localization");
foreach (var language in ConverterGlobals.SupportedLanguages) {
var locFilePath = Path.Combine(baseLocPath, language, $"converter_bookmark_l_{language}.yml");
using var locFileStream = File.OpenWrite(locFilePath);
using var locWriter = new StreamWriter(locFileStream, Encoding.UTF8);
using var locWriter = FileOpeningHelper.OpenWriteWithRetries(locFilePath, Encoding.UTF8);

locWriter.WriteLine($"l_{language}:");

Expand Down
13 changes: 5 additions & 8 deletions ImperatorToCK3/Outputter/CharactersOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using commonItems;
using ImperatorToCK3.CK3.Characters;
using ImperatorToCK3.CommonUtils;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
Expand All @@ -20,15 +21,13 @@ public static void OutputCharacters(string outputModName, CharacterCollection ch
var charactersFromCK3 = characters.Except(charactersFromIR).ToImmutableList();

var pathForCharactersFromIR = $"output/{outputModName}/history/characters/IRToCK3_fromImperator.txt";
using var stream = File.OpenWrite(pathForCharactersFromIR);
using var output = new StreamWriter(stream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(pathForCharactersFromIR);
foreach (var character in charactersFromIR) {
CharacterOutputter.OutputCharacter(output, character, conversionDate);
}

var pathForCharactersFromCK3 = $"output/{outputModName}/history/characters/IRToCK3_fromCK3.txt";
using var stream2 = File.OpenWrite(pathForCharactersFromCK3);
using var output2 = new StreamWriter(stream2, System.Text.Encoding.UTF8);
using var output2 = FileOpeningHelper.OpenWriteWithRetries(pathForCharactersFromCK3, System.Text.Encoding.UTF8);
foreach (var character in charactersFromCK3) {
CharacterOutputter.OutputCharacter(output2, character, conversionDate);
}
Expand All @@ -39,8 +38,7 @@ private static void OutputCharactersDNA(string outputModName, IEnumerable<Charac
Logger.Info("Outputting DNA...");
// Dump all into one file.
var path = Path.Combine("output", outputModName, "common/dna_data/IRToCK3_dna_data.txt");
using var stream = File.OpenWrite(path);
using var output = new StreamWriter(stream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(path, System.Text.Encoding.UTF8);
foreach (var character in charactersWithDNA) {
var dna = character.DNA!;
output.WriteLine($"{dna.Id}={{");
Expand All @@ -59,8 +57,7 @@ private static void OutputPortraitModifiers(string outputModName, IReadOnlyColle
// Enforce hairstyles and beards (otherwise CK3 they will only be used on bookmark screen).
// https://ck3.paradoxwikis.com/Characters_modding#Changing_appearance_through_scripts
var portraitModifiersOutputPath = Path.Combine("output", outputModName, "gfx/portraits/portrait_modifiers/IRToCK3_portrait_modifiers.txt");
using var stream = File.OpenWrite(portraitModifiersOutputPath);
using var output = new StreamWriter(stream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(portraitModifiersOutputPath, System.Text.Encoding.UTF8);

OutputPortraitModifiersForGene("hairstyles", charactersWithDNA, output, conversionDate);
var malesWithBeards = charactersWithDNA
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Outputter/CulturesOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using commonItems;
using commonItems.Serialization;
using ImperatorToCK3.CK3.Cultures;
using ImperatorToCK3.CommonUtils;
using System.IO;

namespace ImperatorToCK3.Outputter;
Expand All @@ -9,8 +10,7 @@ public static class CulturesOutputter {
public static void OutputCultures(string outputModName, CultureCollection cultures) {
Logger.Info("Outputting cultures...");
var outputPath = Path.Combine("output", outputModName, "common/culture/cultures/IRtoCK3_all_cultures.txt");
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

foreach (var culture in cultures) {
output.WriteLine($"{culture.Id}={PDXSerializer.Serialize(culture)}");
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Outputter/DynastiesOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using commonItems.Serialization;
using ImperatorToCK3.CK3.Dynasties;
using ImperatorToCK3.CommonUtils;
using System.IO;
using System.Text;

Expand All @@ -8,8 +9,7 @@ public static class DynastiesOutputter {
public static void OutputDynasties(string outputModName, DynastyCollection dynasties) {
var outputPath = Path.Combine("output", outputModName, "common/dynasties/ir_dynasties.txt");

using FileStream stream = File.OpenWrite(outputPath);
using var output = new StreamWriter(stream, encoding: Encoding.UTF8); // dumping all into one file
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, encoding: Encoding.UTF8); // dumping all into one file
foreach (var dynasty in dynasties) {
output.WriteLine($"{dynasty.Id}={PDXSerializer.Serialize(dynasty, string.Empty)}");
}
Expand Down
10 changes: 4 additions & 6 deletions ImperatorToCK3/Outputter/LocalizationOutputter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using commonItems.Localization;
using commonItems.Mods;
using ImperatorToCK3.CK3;
using ImperatorToCK3.CommonUtils;
using System.Collections.Generic;
using System.IO;

Expand All @@ -16,8 +17,7 @@ public static void OutputLocalization(ModFilesystem irModFS, string outputName,

foreach (var language in ConverterGlobals.SupportedLanguages) {
var locFilePath = Path.Join(baseReplaceLocDir, language, $"converter_l_{language}.yml");
using var locFileStream = File.OpenWrite(locFilePath);
using var locWriter = new StreamWriter(locFileStream, encoding: System.Text.Encoding.UTF8);
using var locWriter = FileOpeningHelper.OpenWriteWithRetries(locFilePath, encoding: System.Text.Encoding.UTF8);

locWriter.WriteLine($"l_{language}:");

Expand Down Expand Up @@ -45,8 +45,7 @@ public static void OutputLocalization(ModFilesystem irModFS, string outputName,
// dynasty localization
foreach (var language in ConverterGlobals.SupportedLanguages) {
var dynastyLocFilePath = Path.Combine(baseLocDir, $"{language}/irtock3_dynasty_l_{language}.yml");
using var dynastyLocStream = File.OpenWrite(dynastyLocFilePath);
using var dynastyLocWriter = new StreamWriter(dynastyLocStream, System.Text.Encoding.UTF8);
using var dynastyLocWriter = FileOpeningHelper.OpenWriteWithRetries(dynastyLocFilePath, System.Text.Encoding.UTF8);

dynastyLocWriter.WriteLine($"l_{language}:");

Expand Down Expand Up @@ -110,8 +109,7 @@ private static void OutputFallbackLockForMissingSecondaryLanguageLoc(string base
Logger.Debug($"Outputting {linesToOutput.Count} fallback loc lines for {language}...");

var locFilePath = Path.Combine(baseLocDir, $"{language}/irtock3_fallback_loc_l_{language}.yml");
using var locFileStream = File.OpenWrite(locFilePath);
using var locWriter = new StreamWriter(locFileStream, System.Text.Encoding.UTF8);
using var locWriter = FileOpeningHelper.OpenWriteWithRetries(locFilePath, System.Text.Encoding.UTF8);

locWriter.WriteLine($"l_{language}:");
foreach (var line in linesToOutput) {
Expand Down
10 changes: 4 additions & 6 deletions ImperatorToCK3/Outputter/MenAtArmsOutputter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using commonItems.Serialization;
using ImperatorToCK3.CK3.Armies;
using ImperatorToCK3.CK3.Characters;
using ImperatorToCK3.CommonUtils;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -13,8 +14,7 @@ namespace ImperatorToCK3.Outputter;
public static class MenAtArmsOutputter {
private static void OutputHiddenEvent(string outputModName, IEnumerable<Character> charactersWithMaa) {
var outputPath = Path.Combine("output", outputModName, "events", "irtock3_hidden_events.txt");
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

output.WriteLine("namespace = irtock3_hidden_events");
output.WriteLine();
Expand All @@ -40,8 +40,7 @@ private static void OutputMenAtArmsTypes(string outputModName, IdObjectCollectio
Logger.Info("Writing men-at-arms types...");

var outputPath = Path.Combine("output", outputModName, "common/men_at_arms_types/IRToCK3_generated_types.txt");
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

foreach (var type in menAtArmsTypes.Where(t=>t.ToBeOutputted)) {
output.WriteLine($"{type.Id}={PDXSerializer.Serialize(type)}");
Expand All @@ -59,8 +58,7 @@ private static void OutputGuiContainer(string outputModName, ModFilesystem modFS
string guiText = File.ReadAllText(hudTopGuiPath);

var outputPath = Path.Combine("output", outputModName, relativeHudTopGuiPath);
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

output.WriteLine(guiText.TrimEnd().TrimEnd('}'));
output.WriteLine("\tcontainer={");
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Outputter/NamedColorsOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using commonItems;
using commonItems.Colors;
using ImperatorToCK3.CommonUtils;
using System.IO;
using System.Linq;

Expand All @@ -22,8 +23,7 @@ public static void OutputNamedColors(string outputModName, NamedColorCollection
Logger.Info("Outputting named colors from Imperator game and mods...");

var outputPath = Path.Combine("output", outputModName, "common", "named_colors", "IRtoCK3_colors_from_Imperator.txt");
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

output.WriteLine("colors = {");
foreach (var (name, color) in diff) {
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Outputter/PillarOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using commonItems;
using commonItems.Serialization;
using ImperatorToCK3.CK3.Cultures;
using ImperatorToCK3.CommonUtils;
using System.IO;

namespace ImperatorToCK3.Outputter;
Expand All @@ -9,8 +10,7 @@ public static class PillarOutputter {
public static void OutputPillars(string outputModName, PillarCollection pillars) {
Logger.Info("Outputting pillars...");
var outputPath = Path.Combine("output", outputModName, "common/culture/pillars/IRtoCK3_all_pillars.txt");
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

foreach (var pillar in pillars) {
output.WriteLine($"{pillar.Id}={PDXSerializer.Serialize(pillar)}");
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Outputter/ProvincesOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ImperatorToCK3.CK3.Provinces;
using ImperatorToCK3.CK3.Titles;
using ImperatorToCK3.CommonUtils;
using System.Collections.Generic;
using System.IO;

Expand Down Expand Up @@ -45,8 +46,7 @@ Title.LandedTitles titles
// Create province mapping file.
if (alreadyOutputtedProvinces.Count != provinces.Count) {
var provinceMappingFilePath = $"output/{outputModName}/history/province_mapping/province_mapping.txt";
using var provinceMappingStream = File.OpenWrite(provinceMappingFilePath);
using var provinceMappingOutput = new StreamWriter(provinceMappingStream, System.Text.Encoding.UTF8);
using var provinceMappingOutput = FileOpeningHelper.OpenWriteWithRetries(provinceMappingFilePath, System.Text.Encoding.UTF8);

foreach (var province in provinces) {
if (alreadyOutputtedProvinces.Contains(province.Id)) {
Expand Down
10 changes: 4 additions & 6 deletions ImperatorToCK3/Outputter/ReligionsOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using commonItems;
using commonItems.Serialization;
using ImperatorToCK3.CK3.Religions;
using ImperatorToCK3.CommonUtils;
using System.IO;
using System.Linq;

Expand All @@ -11,8 +12,7 @@ public static void OutputHolySites(string outputModName, ReligionCollection ck3R
Logger.Info("Writing holy sites...");

var outputPath = Path.Combine("output", outputModName, "common/religion/holy_sites/IRtoCK3_sites.txt");
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

var sitesToOutput = ck3ReligionCollection.HolySites.Where(s => s.IsGeneratedByConverter)
.ToList();
Expand All @@ -23,8 +23,7 @@ public static void OutputHolySites(string outputModName, ReligionCollection ck3R
// Output localization.
foreach (string language in ConverterGlobals.SupportedLanguages) {
var locOutputPath = Path.Combine("output", outputModName, $"localization/{language}/IRtoCK3_holy_sites_l_{language}.yml");
using var locStream = File.OpenWrite(locOutputPath);
using var locWriter = new StreamWriter(locStream, System.Text.Encoding.UTF8);
using var locWriter = FileOpeningHelper.OpenWriteWithRetries(locOutputPath, System.Text.Encoding.UTF8);

locWriter.WriteLine($"l_{language}:");

Expand All @@ -48,8 +47,7 @@ public static void OutputHolySites(string outputModName, ReligionCollection ck3R
public static void OutputReligions(string outputModName, ReligionCollection ck3ReligionCollection) {
Logger.Info("Writing religions...");
var outputPath = Path.Combine("output", outputModName, "common/religion/religions/IRtoCK3_all_religions.txt");
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

foreach (var religion in ck3ReligionCollection) {
output.WriteLine($"{religion.Id}={PDXSerializer.Serialize(religion)}");
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Outputter/SuccessionTriggersOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using commonItems;
using ImperatorToCK3.CK3.Titles;
using ImperatorToCK3.CommonUtils;
using System.Collections.Generic;
using System.IO;

Expand All @@ -8,8 +9,7 @@ public static class SuccessionTriggersOutputter {
public static void OutputSuccessionTriggers(string outputModName, Title.LandedTitles landedTitles, Date ck3BookmarkDate) {
var outputPath = Path.Combine("output", outputModName, "common", "scripted_triggers", "IRToCK3_succession_triggers.txt");

using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

var primogenitureTitles = new List<string>();
var seniorityTitles = new List<string>();
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Outputter/TitlesOutputter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using commonItems.Serialization;
using ImperatorToCK3.CK3.Titles;
using ImperatorToCK3.CommonUtils;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -44,8 +45,7 @@ private static void OutputTitlesHistory(string outputModName, Title.LandedTitles

public static void OutputTitles(string outputModName, Title.LandedTitles titles) {
var outputPath = Path.Combine("output", outputModName, "common", "landed_titles", "00_landed_titles.txt");
using var outputStream = File.OpenWrite(outputPath);
using var output = new StreamWriter(outputStream, System.Text.Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(outputPath, System.Text.Encoding.UTF8);

foreach (var (name, value) in titles.Variables) {
output.WriteLine($"@{name}={value}");
Expand Down
4 changes: 2 additions & 2 deletions ImperatorToCK3/Outputter/WarsOutputter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Text;
using ImperatorToCK3.CK3.Wars;
using ImperatorToCK3.CommonUtils;

namespace ImperatorToCK3.Outputter;

Expand All @@ -11,8 +12,7 @@ public static void OutputWars(string outputModName, IEnumerable<War> wars) {
Logger.Info("Writing wars...");
// dumping all into one file
var path = Path.Combine("output",outputModName, "history/wars/00_wars.txt");
using var stream = File.OpenWrite(path);
using var output = new StreamWriter(stream, Encoding.UTF8);
using var output = FileOpeningHelper.OpenWriteWithRetries(path, Encoding.UTF8);
foreach (var war in wars) {
OutputWar(output, war);
}
Expand Down
Loading

0 comments on commit 0404f04

Please sign in to comment.