Skip to content

Commit 7d1444e

Browse files
committed
merge development into main
2 parents b2281a6 + c03aed9 commit 7d1444e

File tree

13 files changed

+531
-369
lines changed

13 files changed

+531
-369
lines changed

GPUPrefSwitcher/AppEntry.cs

+64-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Text;
45
using System.Windows.Forms;
@@ -8,7 +9,7 @@ namespace GPUPrefSwitcher
89
/// <summary>
910
/// Represents an App Entry in the Registry along with the user's preferences.
1011
/// </summary>
11-
public struct AppEntry
12+
public struct AppEntry : ICloneable
1213
{
1314
public required string AppPath { get; init; }
1415

@@ -44,6 +45,7 @@ readonly get
4445
public required bool SeenInRegistry { get; init; }
4546
public override readonly bool Equals(object obj)
4647
{
48+
4749
return obj is AppEntry entry &&
4850
AppPath == entry.AppPath &&
4951
AppName == entry.AppName &&
@@ -55,35 +57,68 @@ public override readonly bool Equals(object obj)
5557
GPUPrefPluggedIn == entry.GPUPrefPluggedIn &&
5658
RunOnBatteryPath == entry.RunOnBatteryPath &&
5759
RunPluggedInPath == entry.RunPluggedInPath &&
58-
PendingAddToRegistry == entry.PendingAddToRegistry;
60+
PendingAddToRegistry == entry.PendingAddToRegistry &&
61+
SeenInRegistry == entry.SeenInRegistry &&
62+
SwapperStates.SequenceEqual(entry.SwapperStates);
63+
64+
65+
/*
66+
bool yes = obj is AppEntry entry &&
67+
AppPath == entry.AppPath &&
68+
AppName == entry.AppName &&
69+
//appName == entry.appName && //breaks for some reason; null comparison with empty string... let's just exclude this since we're not using it for now
70+
EnableSwitcher == entry.EnableSwitcher &&
71+
EnableFileSwapper == entry.EnableFileSwapper &&
72+
FileSwapperPaths.SequenceEqual(entry.FileSwapperPaths) &&
73+
GPUPrefOnBattery == entry.GPUPrefOnBattery &&
74+
GPUPrefPluggedIn == entry.GPUPrefPluggedIn &&
75+
RunOnBatteryPath == entry.RunOnBatteryPath &&
76+
RunPluggedInPath == entry.RunPluggedInPath &&
77+
PendingAddToRegistry == entry.PendingAddToRegistry &&
78+
SeenInRegistry == entry.SeenInRegistry &&
79+
SwapperStates.SequenceEqual(entry.SwapperStates);
80+
81+
Logger.inst.Log($"Are the same: {yes}: {this} versus {(AppEntry)obj}");
82+
83+
return yes;
84+
*/
5985
}
6086

6187
//Equals() is much faster
6288
public override readonly int GetHashCode()
6389
{
90+
6491
int hashCode = -985154422;
6592
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(AppPath);
66-
//hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(appName); //see above comment for appName
93+
//hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(appName);
6794
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(AppName);
6895
hashCode = hashCode * -1521134295 + EnableSwitcher.GetHashCode();
6996
hashCode = hashCode * -1521134295 + EnableFileSwapper.GetHashCode();
70-
hashCode = hashCode * -1521134295 + GetStringArrHash(FileSwapperPaths);
71-
hashCode = hashCode * -1521134295 + GetStringArrHash(FileSwapperPaths);
97+
hashCode = hashCode * -1521134295 + GetStrArrHash(FileSwapperPaths);
7298
hashCode = hashCode * -1521134295 + GPUPrefOnBattery.GetHashCode();
7399
hashCode = hashCode * -1521134295 + GPUPrefPluggedIn.GetHashCode();
74100
hashCode = hashCode * -1521134295 + RunOnBatteryPath.GetHashCode();
75101
hashCode = hashCode * -1521134295 + RunPluggedInPath.GetHashCode();
76102
hashCode = hashCode * -1521134295 + PendingAddToRegistry.GetHashCode();
103+
hashCode = hashCode * -1521134295 + SeenInRegistry.GetHashCode();
104+
hashCode = hashCode * -1521134295 + GetStrArrHash(from s in SwapperStates select s.ToString()); //a bit hacky but it should work
105+
77106
//TODO: need for AppName
78107
return hashCode;
79108
}
80109

81-
public static int GetStringArrHash(string[] strings)
110+
public static int GetStrArrHash(IEnumerable<string> strs)
82111
{
83112
int hash = -335392656;
84-
for (int i = 0; i < strings.Length; i++)
113+
/*
114+
for (int i = 0; i < objs.Length; i++)
115+
{
116+
hash = hash * -130699793 + objs[i].GetHashCode();
117+
}
118+
*/
119+
foreach(string s in strs)
85120
{
86-
hash = hash * -130699793 + strings[i].GetHashCode();
121+
hash = hash * -130699793 + strs.GetHashCode();
87122
}
88123
return hash;
89124
}
@@ -93,6 +128,7 @@ public override string ToString()
93128
StringBuilder sb = new();
94129
sb.AppendLine($"AppEntry (enabled: {EnableSwitcher}; appname: {AppName}): {AppPath}");
95130
sb.AppendLine($"On Battery: {GPUPrefOnBattery}; Plugged in: On Battery: {GPUPrefPluggedIn}");
131+
sb.AppendLine($"Pending add: {PendingAddToRegistry}");
96132
sb.AppendLine($"File swapper (enabled: {EnableFileSwapper}):");
97133
for (int i = 0; i < FileSwapperPaths.Length; i++)
98134
{
@@ -101,6 +137,25 @@ public override string ToString()
101137
return sb.ToString();
102138
}
103139

140+
public object Clone()
141+
{
142+
return new AppEntry()
143+
{
144+
AppPath = AppPath,
145+
AppName = AppName,
146+
EnableSwitcher = EnableSwitcher,
147+
EnableFileSwapper = EnableFileSwapper,
148+
FileSwapperPaths = (from s in FileSwapperPaths select s).ToArray(),
149+
GPUPrefOnBattery = GPUPrefOnBattery,
150+
GPUPrefPluggedIn = GPUPrefPluggedIn,
151+
RunOnBatteryPath = RunOnBatteryPath,
152+
RunPluggedInPath = RunPluggedInPath,
153+
PendingAddToRegistry = PendingAddToRegistry,
154+
SeenInRegistry = SeenInRegistry,
155+
SwapperStates = (from s in SwapperStates select s).ToArray(),
156+
};
157+
}
158+
104159
public static bool operator ==(AppEntry left, AppEntry right)
105160
{
106161
return left.Equals(right);

GPUPrefSwitcher/AppEntrySaveHandler.cs

+134-36
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,85 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Diagnostics;
45
using System.Linq;
6+
using System.Threading;
7+
using System.Threading.Tasks;
58

69
namespace GPUPrefSwitcher
710
{
11+
12+
/// <summary>
13+
/// Acts as an intermediary or interface with blocking for thread safety when using an <see cref="AppEntrySaveHandler"/>.
14+
/// Usage idea/tip: each function should be responsible for its own borrow and return. That is, if a Task/async function "A"
15+
/// borrows, and then calls another Task/async function "B" that *also* needs to borrow, then "A" should return it before
16+
/// awaiting "B".
17+
/// </summary>
18+
public class AppEntryLibrarian //you could call this AppEntrySaveHandlerCoordinator or AppEntrySaveHandlerThreadCoordinator but that's ridiculous
19+
{
20+
21+
private SemaphoreSlim semaphoreSlim = new(1);
22+
23+
private AppEntrySaveHandler appEntrySaveHandler;
24+
25+
/// <summary>
26+
/// You must call <see cref="Return(AppEntrySaveHandler)"/> after you're done with the <see cref="AppEntrySaveHandler"/> otherwise a deadlock will occur.
27+
/// Throws <see cref="TimeoutException"/> if it waits longer than (a default of) 10 seconds.
28+
/// </summary>
29+
/// <param name="timeout"></param>
30+
/// <returns></returns>
31+
/// <exception cref="TimeoutException"></exception>
32+
public async Task<AppEntrySaveHandler> Borrow(int timeout = 10000)
33+
{
34+
//Logger.inst.Log("Waiting to borrow.");
35+
bool inTime = await semaphoreSlim.WaitAsync(timeout);
36+
if(!inTime)
37+
{
38+
throw new TimeoutException($"Borrowing waited for too long. Check if every Borrow is followed by a Return");
39+
}
40+
41+
//Logger.inst.Log("Borrowed.");
42+
return appEntrySaveHandler;
43+
}
44+
45+
/// <summary>
46+
/// Similar to <see cref="Borrow(int)"/>, but instead of throwing an exception, simply returns null after the timeout.
47+
/// </summary>
48+
/// <param name="timeout"></param>
49+
/// <returns></returns>
50+
public async Task<AppEntrySaveHandler> TryBorrow(int timeout = 10000)
51+
{
52+
bool inTime = await semaphoreSlim.WaitAsync(timeout);
53+
if (!inTime)
54+
{
55+
return null;
56+
}
57+
58+
return appEntrySaveHandler;
59+
}
60+
61+
public void Return(AppEntrySaveHandler appEntrySaveHandler_)
62+
{
63+
if(appEntrySaveHandler != appEntrySaveHandler_)
64+
{
65+
throw new ArgumentException("the same AppEntrySaveHandler instance wasn't returned");
66+
}
67+
68+
semaphoreSlim.Release();
69+
70+
//Logger.inst.Log("Returned.");
71+
}
72+
73+
public AppEntryLibrarian()
74+
{
75+
appEntrySaveHandler = new AppEntrySaveHandler();
76+
}
77+
}
78+
879
/// <summary>
980
/// Used for reading/writing save data for app GPU preferences (<see cref="AppEntry"/>'s).
81+
///
82+
/// This is not thread safe. For such cases, use this with a borrow/return pattern by instantiating an <see cref="AppEntryLibrarian"/>
1083
/// </summary>
1184
public class AppEntrySaveHandler
1285
{
@@ -30,44 +103,47 @@ public void RevertAppEntriesToPrevious()
30103
}
31104
private static List<AppEntry> DeepCopyAppEntries(List<AppEntry> appEntries)
32105
{
33-
AppEntry[] appEntryCopies = appEntries.ToArray();
106+
/*
107+
//AppEntry[] appEntryCopies = appEntries.ToArray();
108+
109+
var appEntryCopies = new AppEntry[appEntries.Count()];
110+
for(int i = 0; i < appEntryCopies.Length; i++)
111+
{
112+
appEntryCopies[i] = appEntries[i]; //struct copy
113+
}
114+
34115
List<AppEntry> newList = new();
35116
newList.AddRange(appEntryCopies);
117+
*/
118+
List<AppEntry> newList = new();
119+
foreach (AppEntry a in appEntries)
120+
{
121+
newList.Add((AppEntry)a.Clone());
122+
}
123+
36124
return newList;
37125
}
38126

39-
40-
public readonly PreferencesXML PreferencesXML;
127+
internal readonly PreferencesXML PreferencesXML;
41128

42129
public AppEntrySaveHandler()
43130
{
131+
44132
PreferencesXML = new PreferencesXML();
45133
prevAppEntries = PreferencesXML.GetAppEntries();
46-
currentAppEntries = PreferencesXML.GetAppEntries();
134+
//currentAppEntries = PreferencesXML.GetAppEntries();
135+
currentAppEntries = DeepCopyAppEntries(prevAppEntries);
136+
47137
}
48138

49-
public void UpdateAppEntryByPath(string path, AppEntry updatedAppEntry)
139+
public void ChangeAppEntryByPath(string path, AppEntry updatedAppEntry)
50140
{
51141

52-
int index = CurrentAppEntries.IndexOf(CurrentAppEntries.Single(x => x.AppPath == path));
53-
54-
/* //for-loop alternative, but the above should throw an error with an obvious enough meaning
55-
int index = -1;
56-
for (int i = 0; i < CurrentAppEntries.Count; i++)
57-
{
58-
AppEntry appEntry = CurrentAppEntries[i];
59-
if (appEntry.AppPath == path)
60-
{
61-
index = i; break;
62-
}
63-
}
142+
int index = currentAppEntries.IndexOf(CurrentAppEntries.Single(x => x.AppPath == path));
64143

65-
if(index<0)
66-
throw new AppEntrySaverException($"UpdateAppEntry: No AppEntry with path {path} was found");
67-
*/
144+
currentAppEntries[index] = updatedAppEntry;
68145

69-
CurrentAppEntries[index] = updatedAppEntry;
70-
146+
//Logger.inst.Log($"new: {CurrentAppEntries[CurrentAppEntries.IndexOf(CurrentAppEntries.Single(x => x.AppPath == path))]}");
71147
}
72148

73149
/*
@@ -83,21 +159,32 @@ public void UpdateAppEntryByPath(string path, AppEntry updatedAppEntry)
83159
*
84160
* UPDATE: Only per-entry eplacement is viable because the XML can still contain stuff that isn't in the registry!!
85161
*/
162+
/// <summary>
163+
/// NOT THREAD SAFE
164+
/// </summary>
86165
public void SaveAppEntryChanges()
87166
{
167+
SaveAppEntryChanges_Internal();
168+
}
88169

170+
private void SaveAppEntryChanges_Internal()
171+
{
89172
List<AppEntry> differences = new();
90-
differences.AddRange(currentAppEntries.Where( entry => NotSameOrInPrevAppEntries(entry) ));
173+
//differences.AddRange(currentAppEntries.Where(entry => NotSameOrInPrevAppEntries(entry)));
174+
foreach(AppEntry a in currentAppEntries)
175+
{
176+
if(NotSameOrInPrevAppEntries(a)) differences.Add(a);
177+
}
91178

92-
List<string> existingAppPaths = PreferencesXML.GetAppPaths().ToList();
179+
List<string> existingAppPaths = new List<string>(from appEntry in PreferencesXML.GetAppEntries() select appEntry.AppPath);
93180
List<AppEntry> needToAdd = new();
94-
needToAdd.AddRange(currentAppEntries.Where( entry => !existingAppPaths.Contains(entry.AppPath) ));
181+
needToAdd.AddRange(currentAppEntries.Where(entry => !existingAppPaths.Contains(entry.AppPath)));
95182

96183
//remove appentries whose *paths* exist in the prev but not the current
97184
List<AppEntry> needToRemoveFromXML = new();
98-
foreach( AppEntry entry in prevAppEntries )
185+
foreach (AppEntry entry in prevAppEntries)
99186
{
100-
if(!currentAppEntries.Exists(a => a.AppPath == entry.AppPath))
187+
if (!currentAppEntries.Exists(a => a.AppPath == entry.AppPath))
101188
{
102189
needToRemoveFromXML.Add(entry);
103190
}
@@ -109,23 +196,34 @@ public void SaveAppEntryChanges()
109196
PreferencesXML.AddAppEntryAndSave(appEntry);
110197
}
111198

112-
foreach(AppEntry appEntry in differences)
199+
foreach (AppEntry appEntry in differences)
113200
{
114201
PreferencesXML.ModifyAppEntryAndSave(appEntry.AppPath, appEntry);
115202
}
116203

117-
foreach(AppEntry appEntry in needToRemoveFromXML)
204+
foreach (AppEntry appEntry in needToRemoveFromXML)
118205
{
119206
bool success = PreferencesXML.TryDeleteAppEntryAndSave(appEntry.AppPath);
120-
if(!success) //this would probably only ever fail if AppEntries were deleted externally whilst the GUI or Service was still running
207+
if (!success) //this would probably only ever fail if AppEntries were deleted externally whilst the GUI or Service was still running
121208
throw new XMLHelperException($"DeleteAppEntry: AppEntry with the specified path not found in data store: {appEntry.AppPath}");
122209
}
123210

124211
prevAppEntries = DeepCopyAppEntries(currentAppEntries); //update the saved entries
125212

126-
Logger.inst.Log("Concluded saving");
213+
Logger.inst.Log($"Concluded saving. Differences: {differences.Count} Added: {needToAdd.Count} Removed: {needToRemoveFromXML.Count}");
127214

128-
bool NotSameOrInPrevAppEntries (AppEntry appEntry)
215+
/*
216+
if (differences.Count > 0)
217+
{
218+
//Logger.inst.Log("Changed:");
219+
foreach (AppEntry appEntry in differences)
220+
{
221+
Logger.inst.Log(appEntry.ToString());
222+
}
223+
}
224+
*/
225+
226+
bool NotSameOrInPrevAppEntries(AppEntry appEntry)
129227
{
130228
return !prevAppEntries.Contains(appEntry); //Contains uses Equals() which is implemented in AppEntry
131229
}
@@ -139,12 +237,12 @@ public bool AppEntriesHaveChangedFromLastSave()
139237
}
140238
else
141239
{
142-
240+
143241
var diffs = currentAppEntries.Except(prevAppEntries);
144242
return true;//move to end for debug info
145243
foreach (AppEntry e in diffs)
146244
{
147-
245+
148246
Debug.WriteLine("CURRENT:");
149247
Debug.WriteLine(e.ToString());
150248

@@ -156,8 +254,8 @@ public bool AppEntriesHaveChangedFromLastSave()
156254
Debug.WriteLine(e.GetHashCode());
157255
Debug.WriteLine(prev.GetHashCode());
158256

159-
Debug.WriteLine($"DIFFERENT?: {(e.Equals(prev)? "no" : "yes")}");
160-
257+
Debug.WriteLine($"DIFFERENT?: {(e.Equals(prev) ? "no" : "yes")}");
258+
161259
/*
162260
Debug.WriteLine(e.EnableFileSwapper.GetHashCode());
163261
Debug.WriteLine(prev.EnableFileSwapper.GetHashCode());

0 commit comments

Comments
 (0)