/ src / settings-ui / Settings.UI.Library / SettingsRepository`1.cs
SettingsRepository`1.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.IO;
  7  using System.Threading;
  8  using ManagedCommon;
  9  using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
 10  
 11  namespace Microsoft.PowerToys.Settings.UI.Library
 12  {
 13      // This Singleton class is a wrapper around the settings configurations that are accessed by viewmodels.
 14      // This class can have only one instance and therefore the settings configurations are common to all.
 15      public sealed class SettingsRepository<T> : ISettingsRepository<T>, IDisposable
 16          where T : class, ISettingsConfig, new()
 17      {
 18          private static readonly Lock _SettingsRepoLock = new Lock();
 19  
 20          private static SettingsUtils _settingsUtils;
 21  
 22          private static SettingsRepository<T> settingsRepository;
 23  
 24          private T settingsConfig;
 25  
 26          private FileSystemWatcher _watcher;
 27  
 28          public event Action<T> SettingsChanged;
 29  
 30          // Suppressing the warning as this is a singleton class and this method is
 31          // necessarily static
 32  #pragma warning disable CA1000 // Do not declare static members on generic types
 33          public static SettingsRepository<T> GetInstance(SettingsUtils settingsUtils)
 34  #pragma warning restore CA1000 // Do not declare static members on generic types
 35          {
 36              // To ensure that only one instance of Settings Repository is created in a multi-threaded environment.
 37              lock (_SettingsRepoLock)
 38              {
 39                  if (settingsRepository == null)
 40                  {
 41                      settingsRepository = new SettingsRepository<T>();
 42                      _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
 43                      settingsRepository.InitializeWatcher();
 44                  }
 45  
 46                  return settingsRepository;
 47              }
 48          }
 49  
 50          // The Singleton class must have a private constructor so that it cannot be instantiated by any other object other than itself.
 51          private SettingsRepository()
 52          {
 53          }
 54  
 55          private void InitializeWatcher()
 56          {
 57              try
 58              {
 59                  var settingsItem = new T();
 60                  var filePath = _settingsUtils.GetSettingsFilePath(settingsItem.GetModuleName());
 61                  var directory = Path.GetDirectoryName(filePath);
 62                  var fileName = Path.GetFileName(filePath);
 63  
 64                  if (!Directory.Exists(directory))
 65                  {
 66                      Directory.CreateDirectory(directory);
 67                  }
 68  
 69                  _watcher = new FileSystemWatcher(directory, fileName);
 70                  _watcher.NotifyFilter = NotifyFilters.LastWrite;
 71                  _watcher.Changed += Watcher_Changed;
 72                  _watcher.EnableRaisingEvents = true;
 73              }
 74              catch (Exception ex)
 75              {
 76                  Logger.LogError($"Failed to initialize settings watcher for {typeof(T).Name}", ex);
 77              }
 78          }
 79  
 80          private void Watcher_Changed(object sender, FileSystemEventArgs e)
 81          {
 82              // Wait a bit for the file write to complete and retry if needed
 83              for (int i = 0; i < 5; i++)
 84              {
 85                  Thread.Sleep(100);
 86                  if (ReloadSettings())
 87                  {
 88                      SettingsChanged?.Invoke(SettingsConfig);
 89                      return;
 90                  }
 91              }
 92          }
 93  
 94          public bool ReloadSettings()
 95          {
 96              try
 97              {
 98                  T settingsItem = new T();
 99                  settingsConfig = _settingsUtils.GetSettings<T>(settingsItem.GetModuleName());
100  
101                  SettingsConfig = settingsConfig;
102  
103                  return true;
104              }
105              catch
106              {
107                  return false;
108              }
109          }
110  
111          // Settings configurations shared across all viewmodels
112          public T SettingsConfig
113          {
114              get
115              {
116                  if (settingsConfig == null)
117                  {
118                      T settingsItem = new T();
119                      settingsConfig = _settingsUtils.GetSettingsOrDefault<T>(settingsItem.GetModuleName());
120                  }
121  
122                  return settingsConfig;
123              }
124  
125              set
126              {
127                  if (value != null)
128                  {
129                      settingsConfig = value;
130                  }
131              }
132          }
133  
134          public void StopWatching()
135          {
136              if (_watcher != null)
137              {
138                  _watcher.EnableRaisingEvents = false;
139              }
140          }
141  
142          public void StartWatching()
143          {
144              if (_watcher != null)
145              {
146                  _watcher.EnableRaisingEvents = true;
147              }
148          }
149  
150          public void Dispose()
151          {
152              _watcher?.Dispose();
153          }
154      }
155  }