Skip to content

Commit a473996

Browse files
committed
Merge branch 'main' into 23-feat-doc-1_0_0-release-documentation-updates
2 parents 5775970 + 1e00520 commit a473996

5 files changed

Lines changed: 87 additions & 37 deletions

File tree

Kepware.Api.Test/ApiClient/ServerUserGroupTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Kepware.Api.Test.ApiClient
1414
{
1515
public class ServerUserGroupTests : TestApiClientBase
1616
{
17-
private const string ENDPOINT_USER_GROUP = "/config/v1/admin/server_user_groups";
17+
private const string ENDPOINT_USER_GROUP = "/config/v1/admin/server_usergroups";
1818

1919
[Fact]
2020
public async Task GetServerUserGroupAsync_ShouldReturnServerUserGroup_WhenApiRespondsSuccessfully()

Kepware.Api.Test/ApiClient/_TestApiClientBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ protected void ConfigureConnectedClient(
9797
var statusResponse = "[{\"Name\": \"ConfigAPI REST Service\", \"Healthy\": true}]";
9898
_httpMessageHandlerMock.SetupRequest(HttpMethod.Get, $"{TEST_ENDPOINT}/config/v1/status")
9999
.ReturnsResponse(HttpStatusCode.OK, statusResponse, "application/json");
100+
101+
_httpMessageHandlerMock.SetupRequest(HttpMethod.Get, $"{TEST_ENDPOINT}/config/v1/doc")
102+
.ReturnsResponse(HttpStatusCode.OK, statusResponse, "application/json");
100103
}
101104

102105
protected Channel CreateTestChannel(string name = "TestChannel", string driver = "Advanced Simulator")

Kepware.Api/KepwareApiClient.cs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ public partial class KepwareApiClient : IKepwareDefaultValueProvider
3131
/// </summary>
3232
public const string UNKNOWN = "Unknown";
3333
private const string ENDPOINT_STATUS = "/config/v1/status";
34+
private const string ENDPOINT_DOC = "/config/v1/doc";
3435
private const string ENDPOINT_ABOUT = "/config/v1/about";
3536

3637
private readonly ILogger<KepwareApiClient> m_logger;
3738
private readonly HttpClient m_httpClient;
3839

39-
private bool? m_blnIsConnected = null;
40+
private bool? m_isConnected = null;
41+
private bool? m_hasValidCredentials = null;
4042

4143
/// <summary>
4244
/// Gets the name of the client instance.
@@ -126,7 +128,7 @@ public async Task<bool> TestConnectionAsync(CancellationToken cancellationToken
126128
bool blnIsConnected = false;
127129
try
128130
{
129-
if (m_blnIsConnected == null) // first time after connection change
131+
if (m_isConnected == null) // first time after connection change
130132
{
131133
m_logger.LogInformation("Connecting to {ClientName}-client at {BaseAddress}...", ClientName, m_httpClient.BaseAddress);
132134
}
@@ -144,7 +146,7 @@ await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false
144146
}
145147
}
146148

147-
if (m_blnIsConnected == null || (m_blnIsConnected != null && m_blnIsConnected != blnIsConnected)) // first time after connection change or when connection is lost
149+
if (m_isConnected == null || (m_isConnected != null && m_isConnected != blnIsConnected)) // first time after connection change or when connection is lost
148150
{
149151
if (!blnIsConnected)
150152
{
@@ -154,16 +156,18 @@ await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false
154156
{
155157
var prodInfo = await GetProductInfoAsync(cancellationToken).ConfigureAwait(false);
156158
m_logger.LogInformation("Successfully connected to {ClientName}-client: {ProductName} {ProductVersion} on {BaseAddress}", ClientName, prodInfo?.ProductName, prodInfo?.ProductVersion, m_httpClient.BaseAddress);
159+
160+
m_hasValidCredentials = await TestCredentialsAsync(cancellationToken).ConfigureAwait(false);
157161
}
158162
}
159163
}
160164
catch (HttpRequestException httpEx)
161165
{
162-
if (m_blnIsConnected == null || m_blnIsConnected == true) // first time after connection change or when connection is lost
166+
if (m_isConnected == null || m_isConnected == true) // first time after connection change or when connection is lost
163167
m_logger.LogWarning(httpEx, "Failed to connect to {ClientName}-client at {BaseAddress}", ClientName, m_httpClient.BaseAddress);
164168
}
165-
m_blnIsConnected = blnIsConnected;
166-
return blnIsConnected;
169+
m_isConnected = blnIsConnected;
170+
return blnIsConnected && m_hasValidCredentials == true;
167171
}
168172

169173
/// <summary>
@@ -182,7 +186,7 @@ await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false
182186
var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
183187
var prodInfo = JsonSerializer.Deserialize(content, KepJsonContext.Default.ProductInfo);
184188

185-
m_blnIsConnected = true;
189+
m_isConnected = true;
186190
return prodInfo;
187191
}
188192
else
@@ -192,8 +196,8 @@ await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false
192196
}
193197
catch (HttpRequestException httpEx)
194198
{
195-
m_logger.LogWarning(httpEx, "Failed to connect to {BaseAddress}", m_httpClient.BaseAddress);
196-
m_blnIsConnected = null;
199+
m_logger.LogWarning(httpEx, "Failed to connect to {ClientName}-client at {BaseAddress}: {Message}", ClientName, m_httpClient.BaseAddress, httpEx.Message);
200+
m_isConnected = null;
197201
}
198202
catch (JsonException jsonEx)
199203
{
@@ -202,6 +206,32 @@ await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false
202206

203207
return null;
204208
}
209+
210+
private async Task<bool> TestCredentialsAsync(CancellationToken cancellationToken = default)
211+
{
212+
bool hasValidCredentials = false;
213+
try
214+
{
215+
var response = await m_httpClient.GetAsync(ENDPOINT_DOC, cancellationToken).ConfigureAwait(false);
216+
hasValidCredentials = response.IsSuccessStatusCode;
217+
if (hasValidCredentials)
218+
{
219+
// credentials are valid
220+
}
221+
else
222+
{
223+
// log a warning, that we don't have valid credentials
224+
m_logger.LogWarning("Failed to connect to {ClientName}-client at {BaseAddress} with valid credentials, Reason: {ReasonPhrase}",
225+
ClientName, m_httpClient.BaseAddress, response.ReasonPhrase);
226+
}
227+
}
228+
catch (HttpRequestException httpEx)
229+
{
230+
m_logger.LogWarning(httpEx, "Failed to connect to {ClientName}-client at {BaseAddress}", ClientName, m_httpClient.BaseAddress);
231+
}
232+
233+
return hasValidCredentials;
234+
}
205235
#endregion
206236

207237
#region IKepwareDefaultValueProvider
@@ -238,7 +268,7 @@ async Task<ReadOnlyDictionary<string, JsonElement>> IKepwareDefaultValueProvider
238268
/// <param name="httpEx"></param>
239269
internal void OnHttpRequestException(HttpRequestException httpEx)
240270
{
241-
m_blnIsConnected = null;
271+
m_isConnected = null;
242272
}
243273
#endregion
244274
}

Kepware.Api/Model/Admin/ServerUserGroup.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Kepware.Api.Model.Admin
1111
/// <summary>
1212
/// Represents a user group in Kepware, allowing management of permissions and access control.
1313
/// </summary>
14-
[Endpoint("/config/v1/admin/server_user_groups/{name}")]
14+
[Endpoint("/config/v1/admin/server_usergroups/{name}")]
1515
public class ServerUserGroup : NamedEntity
1616
{
1717
/// <summary>
@@ -289,7 +289,7 @@ public bool? BrowseNamespace
289289
#endregion
290290
}
291291

292-
[Endpoint("/config/v1/admin/server_user_groups")]
292+
[Endpoint("/config/v1/admin/server_usergroups")]
293293
public class ServerUserGroupCollection : EntityCollection<ServerUserGroup>
294294
{
295295

KepwareSync.Service/SyncService.cs

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
5252
{
5353
m_logger.LogInformation("Starting SyncService...");
5454

55-
await InititalizeAsync();
55+
await InititalizeAsync().ConfigureAwait(false);
5656

5757
if (m_syncOptions.SyncDirection == SyncDirection.DiskToKepware)
5858
{
@@ -67,28 +67,37 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
6767
{
6868
try
6969
{
70-
if (await m_kepServerClient.TestConnectionAsync(stoppingToken))
70+
if (await m_kepServerClient.TestConnectionAsync(stoppingToken).ConfigureAwait(false))
7171
{
72-
if (m_syncOptions.SyncMode == SyncMode.TwoWay ||
73-
m_syncOptions.SyncDirection == SyncDirection.KepwareToDisk)
72+
if (m_changeQueue.TryDequeue(out var changeEvent))
7473
{
75-
if (m_changeQueue.TryDequeue(out var changeEvent))
74+
// check if there are more events for the same source
75+
while (m_changeQueue.TryPeek(out var nextPending) && nextPending.Source == changeEvent!.Source)
7676
{
77-
while (m_changeQueue.TryPeek(out var nextPending) && nextPending.Source == changeEvent!.Source)
78-
{
79-
m_changeQueue.TryDequeue(out changeEvent);
80-
}
81-
await ProcessChangeAsync(changeEvent!, stoppingToken);
77+
m_changeQueue.TryDequeue(out changeEvent);
78+
}
79+
// Process the change event
80+
await ProcessChangeAsync(changeEvent!, stoppingToken).ConfigureAwait(false);
81+
}
82+
// We don't need to read the project Id in "DiskToKepware"-only mode, due to the fact that we are not syncing from Kepware to disk
83+
else if (m_syncOptions.SyncMode == SyncMode.TwoWay || m_syncOptions.SyncDirection != SyncDirection.DiskToKepware)
84+
{
85+
// If we are in two-way sync or Kepware to disk mode, we need to check the current project ID
86+
var currentProjectId = await FetchCurrentProjectIdAsync(m_kepServerClient, stoppingToken).ConfigureAwait(false);
87+
if (m_lastProjectId != currentProjectId)
88+
{
89+
// If the project ID has changed, we need to sync from Kepware to disk
90+
await ProcessChangeAsync(new ChangeEvent { Source = ChangeSource.PrimaryKepServer, Reason = "Project changed" }, stoppingToken).ConfigureAwait(false);
8291
}
8392
else
8493
{
85-
var currentProjectId = await FetchCurrentProjectIdAsync(m_kepServerClient, stoppingToken);
86-
if (m_lastProjectId != currentProjectId)
87-
{
88-
await ProcessChangeAsync(new ChangeEvent { Source = ChangeSource.PrimaryKepServer, Reason = "Project changed" }, stoppingToken);
89-
}
94+
// No changes to process, but we still want to check the connection
9095
}
9196
}
97+
else
98+
{
99+
// No changes to process, but we still want to check the connection
100+
}
92101

93102
blnFirstDisconnect = true;
94103
}
@@ -270,22 +279,30 @@ private async Task SyncProjectToKepServerAsync(string projectSource, Project pro
270279
targetOptions.OverwriteConfigFile, projectSource, clientName, kepServerClient.ClientHostName);
271280

272281
project = await project.CloneAsync(cancellationToken).ConfigureAwait(false);
273-
var overwrite= await RuntimeOverwriteConfig.LoadFromYamlFileAsync(targetOptions.OverwriteConfigFile, cancellationToken).ConfigureAwait(false);
282+
var overwrite = await RuntimeOverwriteConfig.LoadFromYamlFileAsync(targetOptions.OverwriteConfigFile, cancellationToken).ConfigureAwait(false);
274283
overwrite.Apply(project);
275284
}
276285

277-
var (inserts, updates, deletes) = await kepServerClient.Project.CompareAndApply(project, cancellationToken).ConfigureAwait(false);
278-
279-
if (updates > 0 || deletes > 0 || inserts > 0)
286+
if (await kepServerClient.TestConnectionAsync(cancellationToken).ConfigureAwait(false))
280287
{
281-
m_logger.LogInformation("Completed synchronisation from {ProjectSource} to {ClientName}-kepserver ({ClientHostName}): {NumUpdates} updates, {NumInserts} inserts, {NumDeletes} deletes",
282-
projectSource, clientName, kepServerClient.ClientHostName, updates, inserts, deletes);
283-
onSyncedWithChanges?.Invoke();
288+
var (inserts, updates, deletes) = await kepServerClient.Project.CompareAndApply(project, cancellationToken).ConfigureAwait(false);
289+
290+
if (updates > 0 || deletes > 0 || inserts > 0)
291+
{
292+
m_logger.LogInformation("Completed synchronisation from {ProjectSource} to {ClientName}-kepserver ({ClientHostName}): {NumUpdates} updates, {NumInserts} inserts, {NumDeletes} deletes",
293+
projectSource, clientName, kepServerClient.ClientHostName, updates, inserts, deletes);
294+
onSyncedWithChanges?.Invoke();
295+
}
296+
else
297+
{
298+
m_logger.LogInformation("Completed synchronisation from {ProjectSource} to {ClientName}-kepserver ({ClientHostName}):: no changes made",
299+
projectSource, clientName, kepServerClient.ClientHostName);
300+
}
284301
}
285302
else
286303
{
287-
m_logger.LogInformation("Completed synchronisation from {ProjectSource} to {ClientName}-kepserver ({ClientHostName}):: no changes made",
288-
projectSource, clientName, kepServerClient.ClientHostName);
304+
// No connection to the kepware server, log a warning that the sync could not be performed (conection error is alread logged)
305+
m_logger.LogWarning("No connection to {ClientName}-kepserver ({ClientHostName}). Sync from {ProjectSource} skipped.", clientName, kepServerClient.ClientHostName, projectSource);
289306
}
290307
}
291308

0 commit comments

Comments
 (0)