Skip to content

Commit f227fa2

Browse files
committed
Update settings file atomically to prevent locking issues; avoid crashing if file is empty/corrupt
1 parent 386de0f commit f227fa2

2 files changed

Lines changed: 26 additions & 5 deletions

File tree

AutoAudioSwitcher/Program.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Diagnostics;
1313
using System.Reactive;
1414
using System.Reactive.Linq;
15+
using System.Text.Json;
1516

1617
namespace AutoAudioSwitcher;
1718

@@ -20,6 +21,7 @@ internal sealed class Program
2021
private static readonly TimeSpan WindowsAutomaticDefaultDeviceChangeThreshold = TimeSpan.FromSeconds(2);
2122

2223
private const string LogsDirectory = "logs";
24+
public const string SettingsFile = "appsettings.json";
2325

2426
private static readonly LoggingLevelSwitch levelSwitch = new(LogEventLevel.Error);
2527
private static ServiceProvider? provider;
@@ -30,13 +32,19 @@ private static ServiceProvider ConfigureServices()
3032
{
3133
ServiceCollection services = new();
3234

33-
if (!File.Exists("appsettings.json"))
35+
if (!File.Exists(SettingsFile))
3436
{
3537
new Settings().Save();
3638
}
3739

3840
IConfiguration config = new ConfigurationBuilder()
39-
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
41+
.AddJsonFile(SettingsFile, optional: false, reloadOnChange: true)
42+
.SetFileLoadExceptionHandler(e =>
43+
{
44+
// Happens if the file is empty or not a JSON object. This will revert the settings to default; no point
45+
// showing an error here since IConfiguration normally ignores invalid property values anyway.
46+
e.Ignore = e.Exception.GetBaseException() is JsonException;
47+
})
4048
.Build();
4149

4250
services.ConfigureObservable<Settings>(config);

AutoAudioSwitcher/Settings.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,25 @@ internal sealed record Settings : IEquatable<Settings?>
2727
public LogEventLevel LogLevel { get; init; } = LogEventLevel.Error;
2828

2929
/// <summary>
30-
/// Writes the current <see cref="Settings"/> to appsettings.json.
30+
/// Writes the current <see cref="Settings"/> to the settings file.
3131
/// </summary>
3232
public void Save()
3333
{
34-
using FileStream file = File.Open("appsettings.json", FileMode.Create, FileAccess.Write);
35-
JsonSerializer.Serialize(file, this, SettingsSerializerContext.Default.Options);
34+
try
35+
{
36+
string tempFile = $"{Program.SettingsFile}.tmp";
37+
38+
using (FileStream file = File.Open(tempFile, FileMode.Create, FileAccess.Write))
39+
{
40+
JsonSerializer.Serialize(file, this, SettingsSerializerContext.Default.Options);
41+
}
42+
43+
File.Move(tempFile, Program.SettingsFile, overwrite: true);
44+
}
45+
catch (Exception ex)
46+
{
47+
throw new Exception($"Failed to save settings file \"{Path.GetFullPath(Program.SettingsFile)}\".", ex);
48+
}
3649
}
3750

3851
public bool Equals(Settings? other)

0 commit comments

Comments
 (0)