Quellcode durchsuchen

fix: suppress settings file watcher during app saves

Co-authored-by: danielchalmers <[email protected]>
copilot-swe-agent[bot] vor 1 Monat
Ursprung
Commit
69bfc5014a
2 geänderte Dateien mit 84 neuen und 15 gelöschten Zeilen
  1. 56 0
      DesktopClock.Tests/SettingsTests.cs
  2. 28 15
      DesktopClock/Properties/Settings.cs

+ 56 - 0
DesktopClock.Tests/SettingsTests.cs

@@ -66,6 +66,35 @@ public class SettingsPersistenceTests
         Assert.Equal(minHeight, settings.Height);
     }
 
+    [Fact]
+    public void FileChanged_WhenSuppressed_ShouldNotPopulateFromDisk()
+    {
+        using var _ = new TempSettingsFileScope();
+
+        var settings = CreateSettingsInstance();
+        settings.FontFamily = "InMemory";
+        File.WriteAllText(Settings.FilePath, "{\"FontFamily\":\"OnDisk\"}");
+
+        SetSuppressFileChangedEvents(settings, 1);
+        InvokeFileChanged(settings);
+
+        Assert.Equal("InMemory", settings.FontFamily);
+    }
+
+    [Fact]
+    public void FileChanged_WhenNotSuppressed_ShouldPopulateFromDisk()
+    {
+        using var _ = new TempSettingsFileScope();
+
+        var settings = CreateSettingsInstance();
+        settings.FontFamily = "InMemory";
+        File.WriteAllText(Settings.FilePath, "{\"FontFamily\":\"OnDisk\"}");
+
+        InvokeFileChanged(settings);
+
+        Assert.Equal("OnDisk", settings.FontFamily);
+    }
+
     private static Settings CreateSettingsInstance() =>
         (Settings)Activator.CreateInstance(typeof(Settings), nonPublic: true)!;
 
@@ -79,6 +108,33 @@ public class SettingsPersistenceTests
         populateMethod.Invoke(null, new object[] { settings });
     }
 
+    private static void InvokeFileChanged(Settings settings)
+    {
+        var fileChangedMethod = typeof(Settings).GetMethod(
+            "FileChanged",
+            BindingFlags.NonPublic | BindingFlags.Instance);
+
+        Assert.NotNull(fileChangedMethod);
+        fileChangedMethod.Invoke(settings, new object[]
+        {
+            settings,
+            new FileSystemEventArgs(
+                WatcherChangeTypes.Changed,
+                Path.GetDirectoryName(Settings.FilePath)!,
+                Path.GetFileName(Settings.FilePath)),
+        });
+    }
+
+    private static void SetSuppressFileChangedEvents(Settings settings, int value)
+    {
+        var suppressField = typeof(Settings).GetField(
+            "_suppressFileChangedEvents",
+            BindingFlags.NonPublic | BindingFlags.Instance);
+
+        Assert.NotNull(suppressField);
+        suppressField.SetValue(settings, value);
+    }
+
     private sealed class TempSettingsFileScope : IDisposable
     {
         private readonly string _originalFilePath;

+ 28 - 15
DesktopClock/Properties/Settings.cs

@@ -11,6 +11,7 @@ namespace DesktopClock.Properties;
 public sealed class Settings : INotifyPropertyChanged, IDisposable
 {
     private readonly FileSystemWatcher _watcher;
+    private int _suppressFileChangedEvents;
 
     private static readonly Lazy<Settings> _default = new(LoadAndAttemptSave);
 
@@ -297,30 +298,39 @@ public sealed class Settings : INotifyPropertyChanged, IDisposable
     /// </summary>
     public bool Save()
     {
+        System.Threading.Interlocked.Increment(ref _suppressFileChangedEvents);
+
         try
         {
-            var json = JsonConvert.SerializeObject(this, _jsonSerializerSettings);
-
-            // Attempt to save multiple times.
-            for (var i = 0; i < 4; i++)
+            try
             {
-                try
-                {
-                    File.WriteAllText(FilePath, json);
-                    return true;
-                }
-                catch
+                var json = JsonConvert.SerializeObject(this, _jsonSerializerSettings);
+
+                // Attempt to save multiple times.
+                for (var i = 0; i < 4; i++)
                 {
-                    // Wait before next attempt to read.
-                    System.Threading.Thread.Sleep(250);
+                    try
+                    {
+                        File.WriteAllText(FilePath, json);
+                        return true;
+                    }
+                    catch
+                    {
+                        // Wait before next attempt to read.
+                        System.Threading.Thread.Sleep(250);
+                    }
                 }
             }
+            catch (JsonSerializationException)
+            {
+            }
+
+            return false;
         }
-        catch (JsonSerializationException)
+        finally
         {
+            System.Threading.Interlocked.Decrement(ref _suppressFileChangedEvents);
         }
-
-        return false;
     }
 
     /// <summary>
@@ -374,6 +384,9 @@ public sealed class Settings : INotifyPropertyChanged, IDisposable
     /// </summary>
     private void FileChanged(object sender, FileSystemEventArgs e)
     {
+        if (System.Threading.Volatile.Read(ref _suppressFileChangedEvents) > 0)
+            return;
+
         try
         {
             Populate(this);