forked from microsoft/PowerToys
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSettingsUtils.cs
216 lines (190 loc) · 10.2 KB
/
SettingsUtils.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class SettingsUtils : ISettingsUtils
{
private const string DefaultFileName = "settings.json";
private const string DefaultModuleName = "";
private readonly IFile _file;
private readonly ISettingsPath _settingsPath;
public SettingsUtils()
: this(new FileSystem())
{
}
public SettingsUtils(IFileSystem fileSystem)
: this(fileSystem?.File, new SettingPath(fileSystem?.Directory, fileSystem?.Path))
{
}
public SettingsUtils(IFile file, ISettingsPath settingPath)
{
_file = file ?? throw new ArgumentNullException(nameof(file));
_settingsPath = settingPath;
}
public bool SettingsExists(string powertoy = DefaultModuleName, string fileName = DefaultFileName)
{
var settingsPath = _settingsPath.GetSettingsPath(powertoy, fileName);
return _file.Exists(settingsPath);
}
public void DeleteSettings(string powertoy = "")
{
_settingsPath.DeleteSettings(powertoy);
}
public T GetSettings<T>(string powertoy = DefaultModuleName, string fileName = DefaultFileName)
where T : ISettingsConfig, new()
{
if (!SettingsExists(powertoy, fileName))
{
throw new FileNotFoundException();
}
// Given the file already exists, to deserialize the file and read its content.
T deserializedSettings = GetFile<T>(powertoy, fileName);
// If the file needs to be modified, to save the new configurations accordingly.
if (deserializedSettings.UpgradeSettingsConfiguration())
{
SaveSettings(deserializedSettings.ToJsonString(), powertoy, fileName);
}
return deserializedSettings;
}
/// <summary>
/// Get a Deserialized object of the json settings string.
/// This function creates a file in the powertoy folder if it does not exist and returns an object with default properties.
/// </summary>
/// <returns>Deserialized json settings object.</returns>
public T GetSettingsOrDefault<T>(string powertoy = DefaultModuleName, string fileName = DefaultFileName)
where T : ISettingsConfig, new()
{
try
{
return GetSettings<T>(powertoy, fileName);
}
// Catch json deserialization exceptions when the file is corrupt and has an invalid json.
// If there are any deserialization issues like in https://github.com/microsoft/PowerToys/issues/7500, log the error and create a new settings.json file.
// This is different from the case where we have trailing zeros following a valid json file, which we have handled by trimming the trailing zeros.
catch (JsonException ex)
{
Logger.LogError($"Exception encountered while loading {powertoy} settings.", ex);
}
catch (FileNotFoundException)
{
Logger.LogInfo($"Settings file {fileName} for {powertoy} was not found.");
}
// If the settings file does not exist or if the file is corrupt, to create a new object with default parameters and save it to a newly created settings file.
T newSettingsItem = new T();
SaveSettings(newSettingsItem.ToJsonString(), powertoy, fileName);
return newSettingsItem;
}
/// <summary>
/// Get a Deserialized object of the json settings string.
/// This function creates a file in the powertoy folder if it does not exist and returns an object with default properties.
/// </summary>
/// <returns>Deserialized json settings object.</returns>
public T GetSettingsOrDefault<T, T2>(string powertoy = DefaultModuleName, string fileName = DefaultFileName, Func<object, object> settingsUpgrader = null)
where T : ISettingsConfig, new()
where T2 : ISettingsConfig, new()
{
try
{
return GetSettings<T>(powertoy, fileName);
}
// Catch json deserialization exceptions when the file is corrupt and has an invalid json.
// If there are any deserialization issues like in https://github.com/microsoft/PowerToys/issues/7500, log the error and create a new settings.json file.
// This is different from the case where we have trailing zeros following a valid json file, which we have handled by trimming the trailing zeros.
catch (JsonException ex)
{
Logger.LogInfo($"Settings file {fileName} for {powertoy} was unrecognized. Possibly containing an older version. Trying to read again.");
// try to deserialize to the old format, which is presented in T2
try
{
T2 oldSettings = GetSettings<T2>(powertoy, fileName);
T newSettings = (T)settingsUpgrader(oldSettings);
Logger.LogInfo($"Settings file {fileName} for {powertoy} was read successfully in the old format.");
return newSettings;
}
catch (Exception)
{
// do nothing, the problem wasn't that the settings was stored in the previous format, continue with the default settings
Logger.LogError($"{powertoy} settings are corrupt or the format is not supported any longer. Using default settings instead.", ex);
}
}
catch (FileNotFoundException)
{
Logger.LogInfo($"Settings file {fileName} for {powertoy} was not found.");
}
// If the settings file does not exist or if the file is corrupt, to create a new object with default parameters and save it to a newly created settings file.
T newSettingsItem = new T();
SaveSettings(newSettingsItem.ToJsonString(), powertoy, fileName);
return newSettingsItem;
}
// Given the powerToy folder name and filename to be accessed, this function deserializes and returns the file.
private T GetFile<T>(string powertoyFolderName = DefaultModuleName, string fileName = DefaultFileName)
{
// Adding Trim('\0') to overcome possible NTFS file corruption.
// Look at issue https://github.com/microsoft/PowerToys/issues/6413 you'll see the file has a large sum of \0 to fill up a 4096 byte buffer for writing to disk
// This, while not totally ideal, does work around the problem by trimming the end.
// The file itself did write the content correctly but something is off with the actual end of the file, hence the 0x00 bug
var jsonSettingsString = _file.ReadAllText(_settingsPath.GetSettingsPath(powertoyFolderName, fileName)).Trim('\0');
var options = new JsonSerializerOptions { MaxDepth = 0, IncludeFields = true };
return JsonSerializer.Deserialize<T>(jsonSettingsString, options);
}
// Save settings to a json file.
public void SaveSettings(string jsonSettings, string powertoy = DefaultModuleName, string fileName = DefaultFileName)
{
try
{
if (jsonSettings != null)
{
if (!_settingsPath.SettingsFolderExists(powertoy))
{
_settingsPath.CreateSettingsFolder(powertoy);
}
_file.WriteAllText(_settingsPath.GetSettingsPath(powertoy, fileName), jsonSettings);
}
}
catch (Exception e)
{
Logger.LogError($"Exception encountered while saving {powertoy} settings.", e);
#if DEBUG
if (e is ArgumentException || e is ArgumentNullException || e is PathTooLongException)
{
throw;
}
#endif
}
}
// Returns the file path to the settings file, that is exposed from the local ISettingsPath instance.
public string GetSettingsFilePath(string powertoy = "", string fileName = "settings.json")
{
return _settingsPath.GetSettingsPath(powertoy, fileName);
}
/// <summary>
/// Method <c>BackupSettings</c> Mostly a wrapper for SettingsBackupAndRestoreUtils.BackupSettings
/// </summary>
public static (bool Success, string Message, string Severity, bool LastBackupExists, string OptionalMessage) BackupSettings()
{
var settingsBackupAndRestoreUtilsX = SettingsBackupAndRestoreUtils.Instance;
var settingsUtils = new SettingsUtils();
var appBasePath = Path.GetDirectoryName(settingsUtils._settingsPath.GetSettingsPath(string.Empty, string.Empty));
string settingsBackupAndRestoreDir = settingsBackupAndRestoreUtilsX.GetSettingsBackupAndRestoreDir();
return settingsBackupAndRestoreUtilsX.BackupSettings(appBasePath, settingsBackupAndRestoreDir, false);
}
/// <summary>
/// Method <c>RestoreSettings</c> Mostly a wrapper for SettingsBackupAndRestoreUtils.RestoreSettings
/// </summary>
public static (bool Success, string Message, string Severity) RestoreSettings()
{
var settingsBackupAndRestoreUtilsX = SettingsBackupAndRestoreUtils.Instance;
var settingsUtils = new SettingsUtils();
var appBasePath = Path.GetDirectoryName(settingsUtils._settingsPath.GetSettingsPath(string.Empty, string.Empty));
string settingsBackupAndRestoreDir = settingsBackupAndRestoreUtilsX.GetSettingsBackupAndRestoreDir();
return settingsBackupAndRestoreUtilsX.RestoreSettings(appBasePath, settingsBackupAndRestoreDir);
}
}
}