From 80304d124c4c6c59c6b3fa79d903c2c25f697676 Mon Sep 17 00:00:00 2001 From: jericho Date: Sun, 24 Sep 2023 10:19:34 -0400 Subject: [PATCH 01/11] Synchronized serialization context with our model --- Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs index 0b2ab406..f94c4742 100644 --- a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs +++ b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs @@ -137,6 +137,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.PhoneCallRecordingTranscriptTimelineFraction))] [JsonSerializable(typeof(ZoomNet.Models.PhoneCallRecordingTranscriptTimelineUser))] [JsonSerializable(typeof(ZoomNet.Models.PhoneCallRecordingType))] + [JsonSerializable(typeof(ZoomNet.Models.PhoneCallUser))] [JsonSerializable(typeof(ZoomNet.Models.PhoneNumber))] [JsonSerializable(typeof(ZoomNet.Models.PhoneType))] [JsonSerializable(typeof(ZoomNet.Models.PmiMeetingPasswordRequirementType))] @@ -412,6 +413,7 @@ namespace ZoomNet.Json [JsonSerializable(typeof(ZoomNet.Models.PhoneCallRecordingTranscriptTimelineFraction[]))] [JsonSerializable(typeof(ZoomNet.Models.PhoneCallRecordingTranscriptTimelineUser[]))] [JsonSerializable(typeof(ZoomNet.Models.PhoneCallRecordingType[]))] + [JsonSerializable(typeof(ZoomNet.Models.PhoneCallUser[]))] [JsonSerializable(typeof(ZoomNet.Models.PhoneNumber[]))] [JsonSerializable(typeof(ZoomNet.Models.PhoneType[]))] [JsonSerializable(typeof(ZoomNet.Models.PmiMeetingPasswordRequirementType[]))] From 253c070b19caf034a1af3f5a81d8ff0405676221 Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 27 Sep 2023 19:51:45 -0400 Subject: [PATCH 02/11] Handle "Invalid Token" messages Resolves #322 --- Source/ZoomNet/ZoomWebSocketClient.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Source/ZoomNet/ZoomWebSocketClient.cs b/Source/ZoomNet/ZoomWebSocketClient.cs index 30953ed6..36399794 100644 --- a/Source/ZoomNet/ZoomWebSocketClient.cs +++ b/Source/ZoomNet/ZoomWebSocketClient.cs @@ -155,8 +155,21 @@ private async Task ProcessMessage(ResponseMessage msg, CancellationToken cancell { var jsonDoc = JsonDocument.Parse(msg.Text); var module = jsonDoc.RootElement.GetPropertyValue("module", string.Empty); + var success = jsonDoc.RootElement.GetPropertyValue("success", true); var content = jsonDoc.RootElement.GetPropertyValue("content", string.Empty); + if (content.Equals("Invalid Token", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogTrace("{module}. Token is invalid (presumably expired). Requesting a new token...", module); + _tokenHandler.RefreshTokenIfNecessary(true); + return; + } + else if (!success) + { + _logger.LogTrace("FAILURE: Received message: {module}. {content}", module, content); + return; + } + switch (module) { case "build_connection": From 6328f391fcdeb6d77ef8968295a7774cfa638975 Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 1 Nov 2023 13:03:31 -0400 Subject: [PATCH 03/11] All received messages should be processed even when the WebSocket client is shutting down --- .../TestSuites/WebSocketsTestSuite.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Source/ZoomNet.IntegrationTests/TestSuites/WebSocketsTestSuite.cs b/Source/ZoomNet.IntegrationTests/TestSuites/WebSocketsTestSuite.cs index 9146773f..c050a1e9 100644 --- a/Source/ZoomNet.IntegrationTests/TestSuites/WebSocketsTestSuite.cs +++ b/Source/ZoomNet.IntegrationTests/TestSuites/WebSocketsTestSuite.cs @@ -22,11 +22,8 @@ public override async Task RunTestsAsync() var logger = base.LoggerFactory.CreateLogger(); var eventProcessor = new Func(async (webhookEvent, cancellationToken) => { - if (!cancellationToken.IsCancellationRequested) - { - logger.LogInformation("Processing {eventType} event...", webhookEvent.EventType); - await Task.Delay(1, cancellationToken).ConfigureAwait(false); // This async call gets rid of "CS1998 This async method lacks 'await' operators and will run synchronously". - } + logger.LogInformation("Processing {eventType} event...", webhookEvent.EventType); + await Task.Delay(1, cancellationToken).ConfigureAwait(false); // This async call gets rid of "CS1998 This async method lacks 'await' operators and will run synchronously". }); // Configure cancellation (this allows you to press CTRL+C or CTRL+Break to stop the websocket client) From 63cb0639e4eedce7e2c90c22ea185a9be9637dcf Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 1 Nov 2023 13:03:58 -0400 Subject: [PATCH 04/11] Upgrade nuget packages --- .../ZoomNet.IntegrationTests.csproj | 4 ++-- Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/ZoomNet.IntegrationTests/ZoomNet.IntegrationTests.csproj b/Source/ZoomNet.IntegrationTests/ZoomNet.IntegrationTests.csproj index 1c8393ff..e31b6d3c 100644 --- a/Source/ZoomNet.IntegrationTests/ZoomNet.IntegrationTests.csproj +++ b/Source/ZoomNet.IntegrationTests/ZoomNet.IntegrationTests.csproj @@ -12,9 +12,9 @@ - + - + diff --git a/Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj b/Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj index 7c1ea460..97ff3f30 100644 --- a/Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj +++ b/Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj @@ -18,10 +18,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + all runtime; build; native; contentfiles; analyzers From 8bc9a99aacfb61282430b657b3c7f1852ebee63f Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 1 Nov 2023 13:06:17 -0400 Subject: [PATCH 05/11] ConfigureAwait(true) instead of ConfigureAwait(false) in unit testing This is a recommendation from xUnit --- Source/ZoomNet.UnitTests/Extensions/InternalTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs b/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs index df172f6f..c09584ed 100644 --- a/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs +++ b/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs @@ -30,7 +30,7 @@ public async Task ThrowsExceptionWhenExpectedRecordsAreMissing() var response = new MockFluentHttpResponse(message, null, CancellationToken.None); // Act - await Should.ThrowAsync(() => response.AsPaginatedResponse("call_logs")).ConfigureAwait(false); + await Should.ThrowAsync(() => response.AsPaginatedResponse("call_logs")).ConfigureAwait(true); } [Fact] @@ -49,7 +49,7 @@ public async Task DoesNotThrowExceptionWhenRecordsAreMissingAndTotalRecordsIsZer var response = new MockFluentHttpResponse(message, null, CancellationToken.None); // Act - var paginatedResponse = await response.AsPaginatedResponse("call_logs").ConfigureAwait(false); + var paginatedResponse = await response.AsPaginatedResponse("call_logs").ConfigureAwait(true); // Assert paginatedResponse.PageCount.ShouldBe(0); @@ -81,7 +81,7 @@ public async Task ThrowsExceptionWhenExpectedRecordsAreMissing() var response = new MockFluentHttpResponse(message, null, CancellationToken.None); // Act - await Should.ThrowAsync(() => response.AsPaginatedResponseWithToken("call_logs")).ConfigureAwait(false); + await Should.ThrowAsync(() => response.AsPaginatedResponseWithToken("call_logs")).ConfigureAwait(true); } [Fact] @@ -102,7 +102,7 @@ public async Task DoesNotThrowExceptionWhenRecordsAreMissingAndTotalRecordsIsZer var response = new MockFluentHttpResponse(message, null, CancellationToken.None); // Act - var paginatedResponse = await response.AsPaginatedResponseWithToken("call_logs").ConfigureAwait(false); + var paginatedResponse = await response.AsPaginatedResponseWithToken("call_logs").ConfigureAwait(true); // Assert paginatedResponse.PageSize.ShouldBe(100); @@ -132,7 +132,7 @@ public async Task ThrowsExceptionWhenExpectedRecordsAreMissing() var response = new MockFluentHttpResponse(message, null, CancellationToken.None); // Act - await Should.ThrowAsync(() => response.AsPaginatedResponseWithTokenAndDateRange("call_logs")).ConfigureAwait(false); + await Should.ThrowAsync(() => response.AsPaginatedResponseWithTokenAndDateRange("call_logs")).ConfigureAwait(true); } [Fact] @@ -153,7 +153,7 @@ public async Task DoesNotThrowExceptionWhenRecordsAreMissingAndTotalRecordsIsZer var response = new MockFluentHttpResponse(message, null, CancellationToken.None); // Act - var paginatedResponse = await response.AsPaginatedResponseWithTokenAndDateRange("call_logs").ConfigureAwait(false); + var paginatedResponse = await response.AsPaginatedResponseWithTokenAndDateRange("call_logs").ConfigureAwait(true); // Assert paginatedResponse.PageSize.ShouldBe(100); From 8148e0d8abd6584f1d634554a3a080d01977432e Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 1 Nov 2023 13:07:42 -0400 Subject: [PATCH 06/11] Refresh build script --- build.cake | 6 +++--- global.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.cake b/build.cake index 6d874b79..cf74106c 100644 --- a/build.cake +++ b/build.cake @@ -2,9 +2,9 @@ #tool dotnet:?package=GitVersion.Tool&version=5.12.0 #tool dotnet:?package=coveralls.net&version=4.0.1 #tool nuget:?package=GitReleaseManager&version=0.15.0 -#tool nuget:?package=ReportGenerator&version=5.1.25 -#tool nuget:?package=xunit.runner.console&version=2.5.0 -#tool nuget:?package=CodecovUploader&version=0.6.2 +#tool nuget:?package=ReportGenerator&version=5.1.26 +#tool nuget:?package=xunit.runner.console&version=2.6.0 +#tool nuget:?package=CodecovUploader&version=0.7.1 // Install addins. #addin nuget:?package=Cake.Coveralls&version=1.1.0 diff --git a/global.json b/global.json index 3b883f76..2f1e8232 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.400", + "version": "7.0.403", "rollForward": "patch", "allowPrerelease": false } From c6814be43ec5879cddb1cb0758b74617f4022957 Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 1 Nov 2023 13:11:32 -0400 Subject: [PATCH 07/11] Use ConfigureAwait(true) instead of ConfigureAwait(false) in unit tests This is a recommendation from the xUnit analyzer --- Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs | 2 +- Source/ZoomNet.UnitTests/Resources/PhoneCallRecordings.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs b/Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs index 77f5aa07..2752e270 100644 --- a/Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs +++ b/Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs @@ -516,7 +516,7 @@ public async Task GetRecordingsForUserAsync() var recordings = new CloudRecordings(client); // Act - var result = await recordings.GetRecordingsForUserAsync(userId, false, from, to, recordsPerPage, null, CancellationToken.None).ConfigureAwait(false); + var result = await recordings.GetRecordingsForUserAsync(userId, false, from, to, recordsPerPage, null, CancellationToken.None).ConfigureAwait(true); // Assert mockHttp.VerifyNoOutstandingExpectation(); diff --git a/Source/ZoomNet.UnitTests/Resources/PhoneCallRecordings.cs b/Source/ZoomNet.UnitTests/Resources/PhoneCallRecordings.cs index 50a051d0..2523b48f 100644 --- a/Source/ZoomNet.UnitTests/Resources/PhoneCallRecordings.cs +++ b/Source/ZoomNet.UnitTests/Resources/PhoneCallRecordings.cs @@ -31,7 +31,7 @@ public async Task GetRecordingAsync() // Act var result = await phone .GetRecordingAsync(callId, CancellationToken.None) - .ConfigureAwait(false); + .ConfigureAwait(true); // Assert mockHttp.VerifyNoOutstandingExpectation(); @@ -62,7 +62,7 @@ public async Task GetRecordingTranscriptAsync() // Act var result = await phone .GetRecordingTranscriptAsync(recordingId, CancellationToken.None) - .ConfigureAwait(false); + .ConfigureAwait(true); // Assert mockHttp.VerifyNoOutstandingExpectation(); From 4a79f33578941055025eeee9018d096f7eebb50b Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 1 Nov 2023 13:14:41 -0400 Subject: [PATCH 08/11] Make sure the WebSocketClient is stopped when releasing the ZoomWebSocketClient from memory --- Source/ZoomNet/ZoomWebSocketClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/ZoomNet/ZoomWebSocketClient.cs b/Source/ZoomNet/ZoomWebSocketClient.cs index 36399794..7391610b 100644 --- a/Source/ZoomNet/ZoomWebSocketClient.cs +++ b/Source/ZoomNet/ZoomWebSocketClient.cs @@ -205,6 +205,11 @@ private void ReleaseManagedResources() if (_websocketClient != null) { + if (_websocketClient.IsRunning) + { + _websocketClient.Stop(WebSocketCloseStatus.NormalClosure, "Shutting down").GetAwaiter().GetResult(); + } + _websocketClient.Dispose(); _websocketClient = null; } From 5a5e31473d696747a41d7f8206c4d2205daa5508 Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 1 Nov 2023 13:16:37 -0400 Subject: [PATCH 09/11] Formatting --- Source/ZoomNet/Extensions/Internal.cs | 92 ++++++++++++--------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/Source/ZoomNet/Extensions/Internal.cs b/Source/ZoomNet/Extensions/Internal.cs index 908c4ecb..32e59d24 100644 --- a/Source/ZoomNet/Extensions/Internal.cs +++ b/Source/ZoomNet/Extensions/Internal.cs @@ -171,23 +171,19 @@ internal static async Task ReadAsStringAsync(this HttpContent httpConten // This is important: we must make a copy of the response stream otherwise we would get an // exception on subsequent attempts to read the content of the stream - using (var ms = Utils.MemoryStreamManager.GetStream()) - { - const int DefaultBufferSize = 81920; - await contentStream.CopyToAsync(ms, DefaultBufferSize, cancellationToken).ConfigureAwait(false); - ms.Position = 0; - using (var sr = new StreamReader(ms, encoding)) - { + using var ms = Utils.MemoryStreamManager.GetStream(); + const int DefaultBufferSize = 81920; + await contentStream.CopyToAsync(ms, DefaultBufferSize, cancellationToken).ConfigureAwait(false); + ms.Position = 0; + using var sr = new StreamReader(ms, encoding); #if NET7_0_OR_GREATER - content = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + content = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false); #else - content = await sr.ReadToEndAsync().ConfigureAwait(false); + content = await sr.ReadToEndAsync().ConfigureAwait(false); #endif - } - // It's important to rewind the stream - if (contentStream.CanSeek) contentStream.Position = 0; - } + // It's important to rewind the stream + if (contentStream.CanSeek) contentStream.Position = 0; } return content; @@ -525,54 +521,50 @@ internal static T GetPropertyValue(this JsonElement element, string[] names) internal static async Task ForEachAsync(this IEnumerable items, Func> action, int maxDegreeOfParalellism) { var allTasks = new List>(); - using (var throttler = new SemaphoreSlim(initialCount: maxDegreeOfParalellism)) + using var throttler = new SemaphoreSlim(initialCount: maxDegreeOfParalellism); + foreach (var item in items) { - foreach (var item in items) - { - await throttler.WaitAsync(); - allTasks.Add( - Task.Run(async () => + await throttler.WaitAsync(); + allTasks.Add( + Task.Run(async () => + { + try { - try - { - return await action(item).ConfigureAwait(false); - } - finally - { - throttler.Release(); - } - })); - } - - var results = await Task.WhenAll(allTasks).ConfigureAwait(false); - return results; + return await action(item).ConfigureAwait(false); + } + finally + { + throttler.Release(); + } + })); } + + var results = await Task.WhenAll(allTasks).ConfigureAwait(false); + return results; } internal static async Task ForEachAsync(this IEnumerable items, Func action, int maxDegreeOfParalellism) { var allTasks = new List(); - using (var throttler = new SemaphoreSlim(initialCount: maxDegreeOfParalellism)) + using var throttler = new SemaphoreSlim(initialCount: maxDegreeOfParalellism); + foreach (var item in items) { - foreach (var item in items) - { - await throttler.WaitAsync(); - allTasks.Add( - Task.Run(async () => + await throttler.WaitAsync(); + allTasks.Add( + Task.Run(async () => + { + try { - try - { - await action(item).ConfigureAwait(false); - } - finally - { - throttler.Release(); - } - })); - } - - await Task.WhenAll(allTasks).ConfigureAwait(false); + await action(item).ConfigureAwait(false); + } + finally + { + throttler.Release(); + } + })); } + + await Task.WhenAll(allTasks).ConfigureAwait(false); } /// From a4748e904fc4be9c8c87a82264d8bffccd87fa3c Mon Sep 17 00:00:00 2001 From: jericho Date: Wed, 1 Nov 2023 13:22:09 -0400 Subject: [PATCH 10/11] Introduce a way to map multiple string values to the same enum value This is useful for situations where the Zoom API may return different spellings for the same value such as "Windows" and "WIN" for example. In this example, both string values map to ParticipantDevice.Windows --- .../Extensions/InternalTests.cs | 57 +++++++++++++++++++ .../Json/ParticipantDeviceConverter.cs | 1 + Source/ZoomNet/Extensions/Internal.cs | 15 +++++ Source/ZoomNet/Models/ParticipantDevice.cs | 3 +- .../MultipleValuesEnumMemberAttribute.cs | 11 ++++ 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 Source/ZoomNet/Utilities/MultipleValuesEnumMemberAttribute.cs diff --git a/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs b/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs index c09584ed..6480af1b 100644 --- a/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs +++ b/Source/ZoomNet.UnitTests/Extensions/InternalTests.cs @@ -1,11 +1,15 @@ using Shouldly; using System; +using System.ComponentModel; using System.Net; using System.Net.Http; +using System.Runtime.Serialization; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Xunit; using ZoomNet.Models; +using ZoomNet.Utilities; namespace ZoomNet.UnitTests.Extensions { @@ -161,5 +165,58 @@ public async Task DoesNotThrowExceptionWhenRecordsAreMissingAndTotalRecordsIsZer paginatedResponse.TotalRecords.ShouldBe(0); } } + + public class Enumconversion + { + public enum MyEnum + { + [EnumMember(Value = "One")] + First, + + [MultipleValuesEnumMember(DefaultValue = "Two", OtherValues = new[] { "Second", "Alternative" })] + Second, + + [JsonPropertyName("Three")] + Third, + + [Description("Four")] + Fourth, + + Fifth + } + + [Theory] + [InlineData("one", MyEnum.First)] + [InlineData("two", MyEnum.Second)] + [InlineData("second", MyEnum.Second)] + [InlineData("alternative", MyEnum.Second)] + [InlineData("three", MyEnum.Third)] + [InlineData("four", MyEnum.Fourth)] + [InlineData("fifth", MyEnum.Fifth)] + public void ToEnum(string value, MyEnum expected) + { + // Act + var result = value.ToEnum(); + + // Assert + result.ShouldBe(expected); + } + + [Theory] + [InlineData(MyEnum.First, "One")] + [InlineData(MyEnum.Second, "Two")] + [InlineData(MyEnum.Third, "Three")] + [InlineData(MyEnum.Fourth, "Four")] + [InlineData(MyEnum.Fifth, "Fifth")] + public void ToEnumString(MyEnum value, string expected) + { + // Act + var result = value.ToEnumString(); + + // Assert + result.ShouldBe(expected); + + } + } } } diff --git a/Source/ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs b/Source/ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs index 9f331437..0d2044ba 100644 --- a/Source/ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs +++ b/Source/ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs @@ -72,6 +72,7 @@ public void Write_multiple() [InlineData("iOs", ParticipantDevice.IOS)] [InlineData("H.323/SIP", ParticipantDevice.Sip)] [InlineData("Windows", ParticipantDevice.Windows)] + [InlineData("WIN", ParticipantDevice.Windows)] public void Read_single(string value, ParticipantDevice expectedValue) { // Arrange diff --git a/Source/ZoomNet/Extensions/Internal.cs b/Source/ZoomNet/Extensions/Internal.cs index 32e59d24..f0e50333 100644 --- a/Source/ZoomNet/Extensions/Internal.cs +++ b/Source/ZoomNet/Extensions/Internal.cs @@ -756,6 +756,13 @@ internal static string ToEnumString(this T enumValue) internal static bool TryToEnumString(this T enumValue, out string stringValue) where T : Enum { + var multipleValuesEnumMemberAttribute = enumValue.GetAttributeOfType(); + if (multipleValuesEnumMemberAttribute != null) + { + stringValue = multipleValuesEnumMemberAttribute.DefaultValue; + return true; + } + var enumMemberAttribute = enumValue.GetAttributeOfType(); if (enumMemberAttribute != null) { @@ -802,6 +809,14 @@ internal static bool TryToEnum(this string str, out T enumValue) { var customAttributes = enumType.GetField(name).GetCustomAttributes(true); + // See if there's a matching 'MultipleValuesEnumMember' attribute + if (customAttributes.OfType().Any(attribute => string.Equals(attribute.DefaultValue, str, StringComparison.OrdinalIgnoreCase) || + (attribute.OtherValues ?? Array.Empty()).Any(otherValue => string.Equals(otherValue, str, StringComparison.OrdinalIgnoreCase)))) + { + enumValue = (T)Enum.Parse(enumType, name); + return true; + } + // See if there's a matching 'EnumMember' attribute if (customAttributes.OfType().Any(attribute => string.Equals(attribute.Value, str, StringComparison.OrdinalIgnoreCase))) { diff --git a/Source/ZoomNet/Models/ParticipantDevice.cs b/Source/ZoomNet/Models/ParticipantDevice.cs index df56d17f..214f22e2 100644 --- a/Source/ZoomNet/Models/ParticipantDevice.cs +++ b/Source/ZoomNet/Models/ParticipantDevice.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using ZoomNet.Utilities; namespace ZoomNet.Models { @@ -28,7 +29,7 @@ public enum ParticipantDevice /// /// The participant joined via VoIP using a Windows device. /// - [EnumMember(Value = "Windows")] + [MultipleValuesEnumMember(DefaultValue = "Windows", OtherValues = new[] { "WIN" })] Windows, /// diff --git a/Source/ZoomNet/Utilities/MultipleValuesEnumMemberAttribute.cs b/Source/ZoomNet/Utilities/MultipleValuesEnumMemberAttribute.cs new file mode 100644 index 00000000..b9a54b99 --- /dev/null +++ b/Source/ZoomNet/Utilities/MultipleValuesEnumMemberAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace ZoomNet.Utilities +{ + internal class MultipleValuesEnumMemberAttribute : Attribute + { + public string DefaultValue { get; set; } + + public string[] OtherValues { get; set; } + } +} From 07393e99fc3ab6920e7c21006b8729e3eeb6c566 Mon Sep 17 00:00:00 2001 From: jericho Date: Thu, 2 Nov 2023 12:24:54 -0400 Subject: [PATCH 11/11] Add ParticipantDevice.ZoomRoom and accompanying unit test Resolves #324 --- .../ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs | 1 + Source/ZoomNet/Models/ParticipantDevice.cs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs b/Source/ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs index 0d2044ba..410d8b2a 100644 --- a/Source/ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs +++ b/Source/ZoomNet.UnitTests/Json/ParticipantDeviceConverter.cs @@ -73,6 +73,7 @@ public void Write_multiple() [InlineData("H.323/SIP", ParticipantDevice.Sip)] [InlineData("Windows", ParticipantDevice.Windows)] [InlineData("WIN", ParticipantDevice.Windows)] + [InlineData("Zoom Rooms", ParticipantDevice.ZoomRoom)] public void Read_single(string value, ParticipantDevice expectedValue) { // Arrange diff --git a/Source/ZoomNet/Models/ParticipantDevice.cs b/Source/ZoomNet/Models/ParticipantDevice.cs index 214f22e2..8e6f7b09 100644 --- a/Source/ZoomNet/Models/ParticipantDevice.cs +++ b/Source/ZoomNet/Models/ParticipantDevice.cs @@ -54,6 +54,12 @@ public enum ParticipantDevice /// The participant joined via the web. /// [EnumMember(Value = "Web")] - Web + Web, + + /// + /// The participant joined via a Zoom Room. + /// + [EnumMember(Value = "Zoom Rooms")] + ZoomRoom } }