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 }