Skip to content

Commit d1d4ccc

Browse files
committed
made Preferences file thread safe
1 parent 44b57b6 commit d1d4ccc

File tree

2 files changed

+59
-38
lines changed

2 files changed

+59
-38
lines changed

GPUPrefSwitcher/FileSwapper.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class FileSwapper
4444
*
4545
* Writing a function to abstract each part of this path would cause as many problems as it solves for now
4646
*/
47-
public static readonly string SwapPathFolder = Program.SavedDataPath + "SettingsBank";
47+
public static readonly string SwapPathFolder = Path.Combine(Program.SavedDataPath, "SettingsBank/");
4848
public static readonly string OnBatteryPrefix = "OnBattery_";
4949
public static readonly string PluggedInPrefix = "PluggedIn_";
5050

@@ -56,7 +56,7 @@ public string SettingsBankFolderName
5656
}
5757
public string SettingsBankFolderPath
5858
{
59-
get => SwapPathFolder + "\\" + SettingsBankFolderName;
59+
get => Path.Combine(SwapPathFolder, SettingsBankFolderName);
6060
}
6161

6262
public PreferencesXML ForPreferencesXML { get; init; }

GPUPrefSwitcher/PreferencesXML.cs

+57-36
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics;
44
using System.Linq;
55
using System.Text;
6+
using System.Threading;
67
using System.Windows.Forms;
78
using System.Xml;
89
using System.Xml.Linq;
@@ -17,18 +18,41 @@ namespace GPUPrefSwitcher
1718
/// </summary>
1819
public class PreferencesXML
1920
{
21+
public class ThreadSafeXmlDoc : XmlDocument
22+
{
23+
//private XmlDocument xmlDocument = new XmlDocument();
24+
private SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1);
2025

21-
public static readonly string XML_PREFERENCES_PATH = Program.SavedDataPath + "Preferences.xml";
26+
public override void Load(string path)
27+
{
28+
SemaphoreSlim.Wait();
29+
try
30+
{
31+
base.Load(path);
32+
}
33+
finally { SemaphoreSlim.Release(); }
34+
}
35+
36+
public override void Save(string path)
37+
{
38+
SemaphoreSlim.Wait();
39+
try
40+
{
41+
base.Save(path);
42+
}
43+
finally { SemaphoreSlim.Release(); }
44+
}
45+
}
2246

23-
XmlDocument xmlDocument = new XmlDocument();
47+
public static readonly string XML_PREFERENCES_PATH = Program.SavedDataPath + "Preferences.xml";
48+
private ThreadSafeXmlDoc threadSafeXmlDoc = new ThreadSafeXmlDoc();
2449

25-
private object WriteLock = new();
2650
public PreferencesXML()
2751
{
2852

2953
try
3054
{
31-
xmlDocument.Load(XML_PREFERENCES_PATH);
55+
threadSafeXmlDoc.Load(XML_PREFERENCES_PATH);
3256
}
3357
catch (XmlException)
3458
{
@@ -68,7 +92,7 @@ internal void ReloadXML()
6892
{
6993
try
7094
{
71-
xmlDocument.Load(XML_PREFERENCES_PATH);
95+
threadSafeXmlDoc.Load(XML_PREFERENCES_PATH);
7296
}
7397
catch (Exception)
7498
{
@@ -122,7 +146,7 @@ public List<AppEntry> GetAppEntries()
122146

123147
//ReloadXML(); //performance hog?
124148

125-
XmlNodeList xmlAppEntries = xmlDocument.GetElementsByTagName(XML_APP_ENTRY);
149+
XmlNodeList xmlAppEntries = threadSafeXmlDoc.GetElementsByTagName(XML_APP_ENTRY);
126150

127151
List<AppEntry> appEntries = new List<AppEntry>();
128152

@@ -189,7 +213,7 @@ public IEnumerable<string> GetAppPaths()
189213
{
190214

191215
ReloadXML();
192-
XmlNodeList pathEntries = xmlDocument.GetElementsByTagName(XML_APP_PATH);
216+
XmlNodeList pathEntries = threadSafeXmlDoc.GetElementsByTagName(XML_APP_PATH);
193217

194218
foreach (XmlNode pathEntryNode in pathEntries)
195219
{
@@ -219,7 +243,7 @@ XmlNodeList GetAppEntryFileSwapperXmlNodes(XmlNode appEntry)
219243

220244
XmlNode AppEntryNodeByAppPath(string path)
221245
{
222-
XmlNodeList xmlAppEntries = xmlDocument.GetElementsByTagName(XML_APP_ENTRY);
246+
XmlNodeList xmlAppEntries = threadSafeXmlDoc.GetElementsByTagName(XML_APP_ENTRY);
223247

224248
foreach (XmlNode xmlAppEntry in xmlAppEntries)
225249
{
@@ -250,33 +274,33 @@ internal void AddAppEntryAndSave(AppEntry appEntry)
250274
throw new InvalidOperationException($"Tried to add an AppEntry with an AppPath that already exists in the data store: {appEntry.AppPath} — this is undefined behavior.");
251275
}
252276

253-
XmlNode root = xmlDocument.DocumentElement;
277+
XmlNode root = threadSafeXmlDoc.DocumentElement;
254278
{
255-
XmlElement xmlAppEntry = xmlDocument.CreateElement(XML_APP_ENTRY);
256-
XmlElement xmlPath = xmlDocument.CreateElement(XML_APP_PATH);
279+
XmlElement xmlAppEntry = threadSafeXmlDoc.CreateElement(XML_APP_ENTRY);
280+
XmlElement xmlPath = threadSafeXmlDoc.CreateElement(XML_APP_PATH);
257281
{
258282
xmlPath.InnerText = appEntry.AppPath;
259283
{
260-
XmlElement xmlGpuPref = xmlDocument.CreateElement(XML_GPU_PREFERENCE);
284+
XmlElement xmlGpuPref = threadSafeXmlDoc.CreateElement(XML_GPU_PREFERENCE);
261285
{
262286
xmlGpuPref.SetAttribute(XML_ATTR_ENABLESWITCHER, appEntry.EnableSwitcher.ToString());
263287
xmlGpuPref.SetAttribute(XML_ATTR_ENABLEFILESWAPPER, appEntry.EnableFileSwapper.ToString());
264288

265-
XmlElement runOnBattery = xmlDocument.CreateElement(XML_RUN_ON_BATTERY_PATH);
266-
XmlElement runPluggedIn = xmlDocument.CreateElement(XML_RUN_PLUGGED_IN_PATH);
289+
XmlElement runOnBattery = threadSafeXmlDoc.CreateElement(XML_RUN_ON_BATTERY_PATH);
290+
XmlElement runPluggedIn = threadSafeXmlDoc.CreateElement(XML_RUN_PLUGGED_IN_PATH);
267291
xmlGpuPref.AppendChild(runOnBattery);
268292
xmlGpuPref.AppendChild(runPluggedIn);
269293
}
270-
XmlElement xmlPluggedIn = xmlDocument.CreateElement(XML_PLUGGED_IN);
294+
XmlElement xmlPluggedIn = threadSafeXmlDoc.CreateElement(XML_PLUGGED_IN);
271295
{
272296
xmlPluggedIn.InnerText = appEntry.GPUPrefPluggedIn.ToString();
273297
}
274298
//xmlPluggedIn.InnerText = defaultPluggedin;
275-
XmlElement xmlOnBattery = xmlDocument.CreateElement(XML_ON_BATTERY);
299+
XmlElement xmlOnBattery = threadSafeXmlDoc.CreateElement(XML_ON_BATTERY);
276300
{
277301
xmlOnBattery.InnerText = appEntry.GPUPrefOnBattery.ToString();
278302
}
279-
XmlElement xmlFileSwapper = xmlDocument.CreateElement(XML_FILE_SWAPPER);
303+
XmlElement xmlFileSwapper = threadSafeXmlDoc.CreateElement(XML_FILE_SWAPPER);
280304

281305

282306
//xmlOnBattery.InnerText = defaultOnBattery;
@@ -291,17 +315,17 @@ internal void AddAppEntryAndSave(AppEntry appEntry)
291315

292316
if (appEntry.PendingAddToRegistry)
293317
{
294-
XmlElement pendingAdd = xmlDocument.CreateElement(XML_PENDING_ADD);
318+
XmlElement pendingAdd = threadSafeXmlDoc.CreateElement(XML_PENDING_ADD);
295319
xmlAppEntry.AppendChild(pendingAdd);
296320
}
297321

298-
XmlElement seenInRegistry = xmlDocument.CreateElement(XML_SEEN_IN_REGISTRY);
322+
XmlElement seenInRegistry = threadSafeXmlDoc.CreateElement(XML_SEEN_IN_REGISTRY);
299323
seenInRegistry.InnerText = appEntry.SeenInRegistry.ToString();
300324
xmlAppEntry.AppendChild(seenInRegistry);
301325
}
302326
}
303327

304-
xmlDocument.Save(XML_PREFERENCES_PATH);
328+
threadSafeXmlDoc.Save(XML_PREFERENCES_PATH);
305329
}
306330
}
307331

@@ -313,19 +337,16 @@ internal void AddAppEntryAndSave(AppEntry appEntry)
313337
/// <exception cref="XMLHelperException"></exception>
314338
internal bool TryDeleteAppEntryAndSave(string path)
315339
{
316-
lock (WriteLock)
317-
{
318-
ReloadXML();
340+
ReloadXML();
319341

320-
XmlNode xmlAppEntry = AppEntryNodeByAppPath(path);
321-
if (xmlAppEntry == null) { return false; }
342+
XmlNode xmlAppEntry = AppEntryNodeByAppPath(path);
343+
if (xmlAppEntry == null) { return false; }
322344

323-
xmlDocument.DocumentElement.RemoveChild(xmlAppEntry);
345+
threadSafeXmlDoc.DocumentElement.RemoveChild(xmlAppEntry);
324346

325-
xmlDocument.Save(XML_PREFERENCES_PATH);
347+
threadSafeXmlDoc.Save(XML_PREFERENCES_PATH);
326348

327-
return true;
328-
}
349+
return true;
329350
}
330351

331352
public void ModifyAppEntryAndSave(string path, AppEntry newAppEntry) //TODO file swapper functionality
@@ -388,7 +409,7 @@ public void ModifyAppEntryAndSave(string path, AppEntry newAppEntry) //TODO file
388409
//TODO this is jank, I think Switcher.cs needs to not have access to this and only access to AppEntrySaveHandler instead
389410
if (newAppEntry.PendingAddToRegistry)
390411
{
391-
XmlElement pendingAddToRegistry = xmlDocument.CreateElement(XML_PENDING_ADD);
412+
XmlElement pendingAddToRegistry = threadSafeXmlDoc.CreateElement(XML_PENDING_ADD);
392413
if (xmlAppEntry.SelectSingleNode(XML_PENDING_ADD) == null)
393414
{
394415
xmlAppEntry.AppendChild(pendingAddToRegistry);
@@ -419,7 +440,7 @@ public void ModifyAppEntryAndSave(string path, AppEntry newAppEntry) //TODO file
419440
{
420441
string newFileSwapperPath = newFileSwapperPaths[i];
421442

422-
XmlElement xmlElement = xmlDocument.CreateElement(XML_SWAP_PATH);
443+
XmlElement xmlElement = threadSafeXmlDoc.CreateElement(XML_SWAP_PATH);
423444

424445
xmlElement.SetAttribute(XML_ATTR_SWAPPATHSTATUS, PowerLineStatusConversions.PowerLineStatusToOfflineOrOnline(newAppEntry.SwapperStates[i]));//TODO: does this gauruntee order?
425446

@@ -429,7 +450,7 @@ public void ModifyAppEntryAndSave(string path, AppEntry newAppEntry) //TODO file
429450
}
430451
}
431452

432-
xmlDocument.Save(XML_PREFERENCES_PATH);
453+
threadSafeXmlDoc.Save(XML_PREFERENCES_PATH);
433454
}/* catch (NullReferenceException)
434455
{
435456
throw new XmlException($"An error occured while trying to modify AppEntry with AppPath {path}; check if the entry is malformed in Preferences.xml");
@@ -449,7 +470,7 @@ internal void WriteToRegistry(PowerLineStatus powerLineStatus)
449470

450471
//List<string> regPathValues = RegistryHelper.GetGpuPrefPathvalueNames().ToList();
451472

452-
XmlNodeList xmlAppEntries = xmlDocument.GetElementsByTagName(XML_APP_ENTRY);
473+
XmlNodeList xmlAppEntries = threadSafeXmlDoc.GetElementsByTagName(XML_APP_ENTRY);
453474

454475
foreach (XmlNode xmlAppEntry in xmlAppEntries)
455476
{
@@ -643,7 +664,7 @@ private string IdentifyCriticalAppEntryErrors(XmlElement appEntryNode, int lineP
643664

644665
if (sb.ToString() != NO_ERRORS_STRING)
645666
{
646-
string xmlDocumentToString = xmlDocument.OuterXml;
667+
string xmlDocumentToString = threadSafeXmlDoc.OuterXml;
647668
sb.Insert(0, $"The AppEntry node starting on line {linePosition} has the following errors:\n");
648669
}
649670

@@ -660,7 +681,7 @@ public string GetAllCriticalXmlAppEntryErrors()
660681
{
661682
StringBuilder sb = new(NO_ERRORS_STRING);
662683
//apparently you can call SelectNodes() from xmlDocument which returns nothing, so you have to reference DocumentElement first...
663-
var appEntryNodes = xmlDocument.DocumentElement.SelectNodes(XML_APP_ENTRY);
684+
var appEntryNodes = threadSafeXmlDoc.DocumentElement.SelectNodes(XML_APP_ENTRY);
664685
for(int i = 0; i < appEntryNodes.Count; i++)
665686
{
666687
XmlNode node = appEntryNodes[i];
@@ -709,7 +730,7 @@ void CheckForDuplicatePaths()
709730

710731
internal void DebugPrintXML()
711732
{
712-
XmlNodeList nodeList = xmlDocument.GetElementsByTagName(XML_APP_ENTRY);
733+
XmlNodeList nodeList = threadSafeXmlDoc.GetElementsByTagName(XML_APP_ENTRY);
713734

714735
foreach (XmlElement e in nodeList)
715736
{

0 commit comments

Comments
 (0)