1
1
using System ;
2
+ using System . Collections . Concurrent ;
2
3
using System . Collections . Generic ;
3
4
using System . Diagnostics ;
4
5
using System . Linq ;
6
+ using System . Threading ;
7
+ using System . Threading . Tasks ;
5
8
6
9
namespace GPUPrefSwitcher
7
10
{
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
+
8
79
/// <summary>
9
80
/// 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"/>
10
83
/// </summary>
11
84
public class AppEntrySaveHandler
12
85
{
@@ -30,44 +103,47 @@ public void RevertAppEntriesToPrevious()
30
103
}
31
104
private static List < AppEntry > DeepCopyAppEntries ( List < AppEntry > appEntries )
32
105
{
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
+
34
115
List<AppEntry> newList = new();
35
116
newList.AddRange(appEntryCopies);
117
+ */
118
+ List < AppEntry > newList = new ( ) ;
119
+ foreach ( AppEntry a in appEntries )
120
+ {
121
+ newList . Add ( ( AppEntry ) a . Clone ( ) ) ;
122
+ }
123
+
36
124
return newList ;
37
125
}
38
126
39
-
40
- public readonly PreferencesXML PreferencesXML ;
127
+ internal readonly PreferencesXML PreferencesXML ;
41
128
42
129
public AppEntrySaveHandler ( )
43
130
{
131
+
44
132
PreferencesXML = new PreferencesXML ( ) ;
45
133
prevAppEntries = PreferencesXML . GetAppEntries ( ) ;
46
- currentAppEntries = PreferencesXML . GetAppEntries ( ) ;
134
+ //currentAppEntries = PreferencesXML.GetAppEntries();
135
+ currentAppEntries = DeepCopyAppEntries ( prevAppEntries ) ;
136
+
47
137
}
48
138
49
- public void UpdateAppEntryByPath ( string path , AppEntry updatedAppEntry )
139
+ public void ChangeAppEntryByPath ( string path , AppEntry updatedAppEntry )
50
140
{
51
141
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 ) ) ;
64
143
65
- if(index<0)
66
- throw new AppEntrySaverException($"UpdateAppEntry: No AppEntry with path {path} was found");
67
- */
144
+ currentAppEntries [ index ] = updatedAppEntry ;
68
145
69
- CurrentAppEntries [ index ] = updatedAppEntry ;
70
-
146
+ //Logger.inst.Log($"new: {CurrentAppEntries[CurrentAppEntries.IndexOf(CurrentAppEntries.Single(x => x.AppPath == path))]}");
71
147
}
72
148
73
149
/*
@@ -83,21 +159,32 @@ public void UpdateAppEntryByPath(string path, AppEntry updatedAppEntry)
83
159
*
84
160
* UPDATE: Only per-entry eplacement is viable because the XML can still contain stuff that isn't in the registry!!
85
161
*/
162
+ /// <summary>
163
+ /// NOT THREAD SAFE
164
+ /// </summary>
86
165
public void SaveAppEntryChanges ( )
87
166
{
167
+ SaveAppEntryChanges_Internal ( ) ;
168
+ }
88
169
170
+ private void SaveAppEntryChanges_Internal ( )
171
+ {
89
172
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
+ }
91
178
92
- List < string > existingAppPaths = PreferencesXML . GetAppPaths ( ) . ToList ( ) ;
179
+ List < string > existingAppPaths = new List < string > ( from appEntry in PreferencesXML . GetAppEntries ( ) select appEntry . AppPath ) ;
93
180
List < AppEntry > needToAdd = new ( ) ;
94
- needToAdd . AddRange ( currentAppEntries . Where ( entry => ! existingAppPaths . Contains ( entry . AppPath ) ) ) ;
181
+ needToAdd . AddRange ( currentAppEntries . Where ( entry => ! existingAppPaths . Contains ( entry . AppPath ) ) ) ;
95
182
96
183
//remove appentries whose *paths* exist in the prev but not the current
97
184
List < AppEntry > needToRemoveFromXML = new ( ) ;
98
- foreach ( AppEntry entry in prevAppEntries )
185
+ foreach ( AppEntry entry in prevAppEntries )
99
186
{
100
- if ( ! currentAppEntries . Exists ( a => a . AppPath == entry . AppPath ) )
187
+ if ( ! currentAppEntries . Exists ( a => a . AppPath == entry . AppPath ) )
101
188
{
102
189
needToRemoveFromXML . Add ( entry ) ;
103
190
}
@@ -109,23 +196,34 @@ public void SaveAppEntryChanges()
109
196
PreferencesXML . AddAppEntryAndSave ( appEntry ) ;
110
197
}
111
198
112
- foreach ( AppEntry appEntry in differences )
199
+ foreach ( AppEntry appEntry in differences )
113
200
{
114
201
PreferencesXML . ModifyAppEntryAndSave ( appEntry . AppPath , appEntry ) ;
115
202
}
116
203
117
- foreach ( AppEntry appEntry in needToRemoveFromXML )
204
+ foreach ( AppEntry appEntry in needToRemoveFromXML )
118
205
{
119
206
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
121
208
throw new XMLHelperException ( $ "DeleteAppEntry: AppEntry with the specified path not found in data store: { appEntry . AppPath } ") ;
122
209
}
123
210
124
211
prevAppEntries = DeepCopyAppEntries ( currentAppEntries ) ; //update the saved entries
125
212
126
- Logger . inst . Log ( "Concluded saving" ) ;
213
+ Logger . inst . Log ( $ "Concluded saving. Differences: { differences . Count } Added: { needToAdd . Count } Removed: { needToRemoveFromXML . Count } ") ;
127
214
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 )
129
227
{
130
228
return ! prevAppEntries . Contains ( appEntry ) ; //Contains uses Equals() which is implemented in AppEntry
131
229
}
@@ -139,12 +237,12 @@ public bool AppEntriesHaveChangedFromLastSave()
139
237
}
140
238
else
141
239
{
142
-
240
+
143
241
var diffs = currentAppEntries . Except ( prevAppEntries ) ;
144
242
return true ; //move to end for debug info
145
243
foreach ( AppEntry e in diffs )
146
244
{
147
-
245
+
148
246
Debug . WriteLine ( "CURRENT:" ) ;
149
247
Debug . WriteLine ( e . ToString ( ) ) ;
150
248
@@ -156,8 +254,8 @@ public bool AppEntriesHaveChangedFromLastSave()
156
254
Debug . WriteLine ( e . GetHashCode ( ) ) ;
157
255
Debug . WriteLine ( prev . GetHashCode ( ) ) ;
158
256
159
- Debug . WriteLine ( $ "DIFFERENT?: { ( e . Equals ( prev ) ? "no" : "yes" ) } ") ;
160
-
257
+ Debug . WriteLine ( $ "DIFFERENT?: { ( e . Equals ( prev ) ? "no" : "yes" ) } ") ;
258
+
161
259
/*
162
260
Debug.WriteLine(e.EnableFileSwapper.GetHashCode());
163
261
Debug.WriteLine(prev.EnableFileSwapper.GetHashCode());
0 commit comments