editor/AdvancedMappingTools/Core/StoreSafety.cs

Editor helper that reads and writes local JSON settings files for the Advanced Mapping Tools addon. Read deserializes a file and logs a warning on failure, Write writes to a temp file, backs up a corrupt previous file, then replaces the original.

File Access
namespace AdvancedMappingTools;
/// <summary>Read damaged local settings without taking the editor UI down; preserve them on the next save.</summary>
internal static class StoreSafety
{
	static readonly Dictionary<string, DateTime> Reported = [];
	internal static void Warn( string path, string problem )
	{
		DateTime stamp;
		try
		{
			stamp = System.IO.File.GetLastWriteTimeUtc( path );
		}
		catch
		{
			stamp = DateTime.MinValue;
		}

		if ( Reported.TryGetValue( path, out var previous ) && previous == stamp )
			return;
		Reported[path] = stamp;
		Log.Warning( $"AMT settings: {System.IO.Path.GetFileName( path )} could not be used: {problem}. The original file was kept." );
	}

	internal static T Read<T>( string path )
	{
		if ( !System.IO.File.Exists( path ) )
			return default;
		try
		{
			return System.Text.Json.JsonSerializer.Deserialize<T>( System.IO.File.ReadAllText( path ) );
		}
		catch ( Exception ex )when ( ex is System.Text.Json.JsonException or System.IO.IOException or UnauthorizedAccessException )
		{
			Warn( path, ex.Message );
			return default;
		}
	}

	internal static void Write( string path, string json )
	{
		System.IO.Directory.CreateDirectory( System.IO.Path.GetDirectoryName( path ) );
		var temporary = path + "." + Guid.NewGuid().ToString( "N" ) + ".tmp";
		try
		{
			System.IO.File.WriteAllText( temporary, json );
			if ( System.IO.File.Exists( path ) )
			{
				try
				{
					using var previous = System.Text.Json.JsonDocument.Parse( System.IO.File.ReadAllText( path ) );
				}
				catch ( System.Text.Json.JsonException )
				{
					var recovery = path + ".corrupt_" + DateTime.UtcNow.ToString( "yyyyMMdd_HHmmss" ) + "_" + Guid.NewGuid().ToString( "N" );
					System.IO.File.Copy( path, recovery, false );
					Log.Warning( $"AMT settings: the damaged previous file was preserved as {System.IO.Path.GetFileName( recovery )}." );
				}

				System.IO.File.Replace( temporary, path, null );
			}
			else
				System.IO.File.Move( temporary, path );
			Reported.Remove( path );
		}
		finally
		{
			if ( System.IO.File.Exists( temporary ) )
				System.IO.File.Delete( temporary );
		}
	}
}