Skip to content

Commit f739113

Browse files
authored
Merge branch 'develop' into main
2 parents 2b331e6 + 0d37dec commit f739113

44 files changed

Lines changed: 3135 additions & 950 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

roles/lib/files/FWO.Api.Client/GraphQlApiSubscription.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class GraphQlApiSubscription<SubscriptionResponseType> : ApiSubscription,
1212

1313
private IObservable<GraphQLResponse<dynamic>> subscriptionStream = null!;
1414
private IDisposable subscription = null!;
15+
private readonly ApiConnection apiConnection;
1516
private readonly GraphQLHttpClient graphQlClient;
1617
private readonly GraphQLRequest request;
1718
private readonly Action<Exception> internalExceptionHandler;
@@ -22,6 +23,7 @@ public void Initialize()
2223
}
2324
public GraphQlApiSubscription(ApiConnection apiConnection, GraphQLHttpClient graphQlClient, GraphQLRequest request, Action<Exception> exceptionHandler, SubscriptionUpdate OnUpdate)
2425
{
26+
this.apiConnection = apiConnection;
2527
this.OnUpdate = OnUpdate;
2628
this.graphQlClient = graphQlClient;
2729
this.request = request;
@@ -91,15 +93,18 @@ protected virtual void CreateSubscription()
9193

9294
private void ApiConnectionOnAuthHeaderChanged(object? sender, string jwt)
9395
{
94-
subscription.Dispose();
96+
subscription?.Dispose();
9597
CreateSubscription();
9698
}
9799

98100
protected override void Dispose(bool disposing)
99101
{
100102
if (disposing)
101103
{
102-
subscription.Dispose();
104+
subscription?.Dispose();
105+
106+
// Important: detach from ApiConnection event to avoid keeping this subscription alive.
107+
apiConnection.OnAuthHeaderChanged -= ApiConnectionOnAuthHeaderChanged;
103108
}
104109
}
105110
}

roles/lib/files/FWO.Config.Api/Config.cs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace FWO.Config.Api
1010
{
11-
public abstract class Config : ConfigData
11+
public abstract class Config : ConfigData, IDisposable
1212
{
1313
/// <summary>
1414
/// Internal connection to api server. Used to get/edit config data.
@@ -22,8 +22,12 @@ public abstract class Config : ConfigData
2222

2323
protected SemaphoreSlim semaphoreSlim = new(1, 1);
2424

25+
private GraphQlApiSubscription<ConfigItem[]>? configSubscription;
26+
2527
public ConfigItem[] RawConfigItems { get; set; } = [];
2628

29+
private bool disposedValue;
30+
2731
protected Config() { }
2832

2933
protected Config(ApiConnection apiConnection, int userId, bool withSubscription = false)
@@ -34,18 +38,25 @@ protected Config(ApiConnection apiConnection, int userId, bool withSubscription
3438
public async Task InitWithUserId(ApiConnection apiConnection, int userId, bool withSubscription = false)
3539
{
3640
this.apiConnection = apiConnection;
37-
if(withSubscription) // used in Ui context
41+
42+
// Always set UserId, even when no subscription is used.
43+
UserId = userId;
44+
45+
if (withSubscription) // used in Ui context
3846
{
39-
UserId = userId;
47+
// Re-init (e.g. login) can happen; dispose previous subscription to avoid handler accumulation.
48+
configSubscription?.Dispose();
49+
configSubscription = null;
50+
4051
List<string> ignoreKeys = []; // currently nothing ignored, may be used later
41-
apiConnection.GetSubscription<ConfigItem[]>(SubscriptionExceptionHandler, SubscriptionUpdateHandler,
42-
ConfigQueries.subscribeConfigChangesByUser, new { UserId , ignoreKeys });
52+
configSubscription = apiConnection.GetSubscription<ConfigItem[]>(SubscriptionExceptionHandler, SubscriptionUpdateHandler,
53+
ConfigQueries.subscribeConfigChangesByUser, new { UserId, ignoreKeys });
4354
await Task.Run(async () => { while (!Initialized) { await Task.Delay(10); } }); // waitForFirstUpdate
4455
}
4556
else // when only simple read is needed, e.g. during scheduled report in middleware server
4657
{
4758
ConfigItem[] configItems = await apiConnection.SendQueryAsync<ConfigItem[]>(ConfigQueries.getConfigItemsByUser, new { User = UserId });
48-
if(configItems.Length > 0)
59+
if (configItems.Length > 0)
4960
{
5061
Update(configItems);
5162
RawConfigItems = configItems;
@@ -96,7 +107,7 @@ protected void Update(ConfigItem[] configItems)
96107
}
97108
}
98109
}
99-
foreach(var name in remainingConfigItemNames.Where(n => !n.Contains("StateMatrix"))) // StateMatrix ConfigItems are handled separately
110+
foreach (var name in remainingConfigItemNames.Where(n => !n.Contains("StateMatrix"))) // StateMatrix ConfigItems are handled separately
100111
{
101112
Log.WriteDebug($"Load {(UserId == 0 ? "Global " : "")}Config Items", $"Config item with key \"{name}\" could not be found. {(UserId == 0 ? "" : "User might not have customized the setting. ")}Using default value.");
102113
}
@@ -144,7 +155,7 @@ public async Task<ConfigData> GetEditableConfig()
144155
{
145156
await semaphoreSlim.WaitAsync();
146157
try
147-
{
158+
{
148159
return (ConfigData)CloneEditable();
149160
}
150161
finally { semaphoreSlim.Release(); }
@@ -160,6 +171,37 @@ protected void InvokeOnChange(Config config, ConfigItem[] configItems)
160171
OnChange?.Invoke(config, configItems);
161172
}
162173

174+
protected virtual void Dispose(bool disposing)
175+
{
176+
if (!disposedValue)
177+
{
178+
if (disposing)
179+
{
180+
// Stop GraphQL subscription so it no longer holds references to this instance.
181+
configSubscription?.Dispose();
182+
configSubscription = null;
183+
184+
// Dispose SemaphoreSlim
185+
semaphoreSlim?.Dispose();
186+
187+
// Clear all event subscribers
188+
OnChange = null;
189+
}
190+
disposedValue = true;
191+
}
192+
}
193+
194+
public void Dispose()
195+
{
196+
Dispose(disposing: true);
197+
GC.SuppressFinalize(this);
198+
}
199+
200+
~Config()
201+
{
202+
Dispose(disposing: false);
203+
}
204+
163205
public abstract string GetText(string key);
164206
}
165207
}

roles/lib/files/FWO.Config.Api/UserConfig.cs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public UserConfig() : base()
5757
{
5858
User = new UiUser();
5959
}
60-
60+
6161
private void OnGlobalConfigChange(Config config, ConfigItem[] changedItems)
6262
{
6363
// Get properties that belong to the user config
@@ -74,7 +74,7 @@ private void OnGlobalConfigChange(Config config, ConfigItem[] changedItems)
7474

7575
public async Task SetUserInformation(string userDn, ApiConnection apiConnection)
7676
{
77-
if(globalConfig != null)
77+
if (globalConfig != null)
7878
{
7979
OnGlobalConfigChange(globalConfig, globalConfig.RawConfigItems);
8080
}
@@ -95,11 +95,11 @@ public async Task SetUserInformation(string userDn, ApiConnection apiConnection)
9595

9696
public async Task ChangeLanguage(string languageName, ApiConnection apiConnection)
9797
{
98-
if(globalConfig != null)
98+
if (globalConfig != null)
9999
{
100100
await apiConnection.SendQueryAsync<ReturnId>(AuthQueries.updateUserLanguage, new { id = User.DbId, language = languageName });
101101
Translate = globalConfig.LangDict[languageName];
102-
Overwrite = apiConnection != null ? await GetCustomDict(languageName): globalConfig.OverDict[languageName];
102+
Overwrite = apiConnection != null ? await GetCustomDict(languageName) : globalConfig.OverDict[languageName];
103103
User.Language = languageName;
104104
InvokeOnChange(this, []);
105105
}
@@ -112,8 +112,12 @@ public string GetUserLanguage()
112112

113113
public void SetLanguage(string languageName)
114114
{
115-
User = new UiUser(){ Language = languageName != null && languageName != "" ? languageName :
116-
globalConfig != null ? globalConfig.DefaultLanguage : GlobalConst.kEnglish};
115+
string defaultLanguage = globalConfig != null ? globalConfig.DefaultLanguage : GlobalConst.kEnglish;
116+
117+
User = new UiUser()
118+
{
119+
Language = languageName != null && languageName != "" ? languageName : defaultLanguage
120+
};
117121
if (globalConfig != null && globalConfig.LangDict.TryGetValue(User.Language, out Dictionary<string, string>? langDict))
118122
{
119123
Translate = langDict;
@@ -133,7 +137,7 @@ public override string GetText(string key)
133137
}
134138
else
135139
{
136-
if(globalConfig != null)
140+
if (globalConfig != null)
137141
{
138142
string defaultLanguage = globalConfig.DefaultLanguage;
139143
if (defaultLanguage == "")
@@ -160,15 +164,15 @@ public string PureLine(string text)
160164

161165
public static string PureLineStat(string text)
162166
{
163-
var regex = new Regex(@"\s", RegexOptions.None, TimeSpan.FromSeconds(1));
164-
string output = RemoveLinks(regex.Replace(text.Trim(), " "));
167+
var regex = new Regex(@"\s", RegexOptions.None, TimeSpan.FromSeconds(1));
168+
string output = RemoveLinks(regex.Replace(text.Trim(), " "));
165169
output = ReplaceListElems(output);
166170
bool cont = true;
167-
while(cont)
171+
while (cont)
168172
{
169173
string outputOrig = output;
170174
output = Regex.Replace(outputOrig, @" ", " ");
171-
if(output.Length == outputOrig.Length)
175+
if (output.Length == outputOrig.Length)
172176
{
173177
cont = false;
174178
}
@@ -248,7 +252,7 @@ private static string RemoveLinks(string txtString)
248252
txtString = Regex.Replace(txtString, "</a>", "");
249253
return txtString;
250254
}
251-
255+
252256
private static string ReplaceListElems(string txtString)
253257
{
254258
txtString = Regex.Replace(txtString, "<ol>", "");
@@ -260,7 +264,7 @@ private static string ReplaceListElems(string txtString)
260264
txtString = Regex.Replace(txtString, "<br>", "\r\n");
261265
return txtString;
262266
}
263-
267+
264268
private string Convert(string rawText)
265269
{
266270
string plainText = System.Web.HttpUtility.HtmlDecode(rawText);
@@ -300,7 +304,13 @@ private string Convert(string rawText)
300304
return plainText;
301305
}
302306

303-
protected virtual void Dispose(bool disposing)
307+
public new void Dispose()
308+
{
309+
Dispose(disposing: true);
310+
GC.SuppressFinalize(this);
311+
}
312+
313+
protected override void Dispose(bool disposing)
304314
{
305315
if (!disposedValue)
306316
{
@@ -310,12 +320,7 @@ protected virtual void Dispose(bool disposing)
310320
}
311321
disposedValue = true;
312322
}
313-
}
314-
315-
public void Dispose()
316-
{
317-
Dispose(disposing: true);
318-
GC.SuppressFinalize(this);
323+
base.Dispose(disposing); // Call base class dispose
319324
}
320325
}
321326
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
3+
namespace FWO.Data.Middleware
4+
{
5+
/// <summary>Represents a Quartz job exposed to the UI.</summary>
6+
public class SchedulerJobInfo
7+
{
8+
public string JobName { get; set; } = "";
9+
public string Group { get; set; } = "";
10+
public DateTimeOffset? NextFireTimeUtc { get; set; }
11+
public DateTimeOffset? LastFireTimeUtc { get; set; }
12+
public string IntervalDescription { get; set; } = "";
13+
public string LastExecutionStatus { get; set; } = "";
14+
public string LastExecutionError { get; set; } = "";
15+
}
16+
17+
/// <summary>Request payload to trigger a job manually.</summary>
18+
public class SchedulerJobTriggerParameters
19+
{
20+
public string JobName { get; set; } = "";
21+
}
22+
}

0 commit comments

Comments
 (0)