diff --git a/src/TwitchLib.Communication.Tests/Clients/ClientTestsBase.cs b/src/TwitchLib.Communication.Tests/Clients/ClientTestsBase.cs
index 14b10cd..7894e57 100644
--- a/src/TwitchLib.Communication.Tests/Clients/ClientTestsBase.cs
+++ b/src/TwitchLib.Communication.Tests/Clients/ClientTestsBase.cs
@@ -11,640 +11,640 @@
using Xunit.Sdk;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
-namespace TwitchLib.Communication.Tests.Clients
+namespace TwitchLib.Communication.Tests.Clients;
+
+///
+/// bundles -Tests in one container
+///
+///
+///
+///
+///
+///
+///
+///
+///
+public abstract class ClientTestsBase where T : IClient
{
- ///
- /// bundles -Tests in one container
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public abstract class ClientTestsBase where T : IClient
- {
- private static TimeSpan WaitOneDuration => TimeSpan.FromSeconds(5);
- private readonly IClientOptions? _options;
-
- protected ClientTestsBase(IClientOptions? options = null)
+ private static TimeSpan WaitOneDuration => TimeSpan.FromSeconds(5);
+ private readonly IClientOptions? _options;
+
+ protected ClientTestsBase(IClientOptions? options = null)
+ {
+ _options = options;
+ }
+
+ [Fact]
+ public async Task Client_Raises_OnConnected_EventArgs()
+ {
+ // create one logger per test-method! - cause one file per test-method is generated
+ var logger = TestLogHelper.GetLogger();
+ var client = GetClient(logger, _options);
+ Assert.NotNull(client);
+ try
+ {
+ var pauseConnected = new ManualResetEvent(false);
+
+ await Assert.RaisesAsync(
+ h => client.OnConnected += h,
+ h => client.OnConnected -= h,
+ async () =>
+ {
+ client.OnConnected += async(sender, e) =>
+ {
+ pauseConnected.Set();
+ };
+
+ await client.OpenAsync();
+ Assert.True(pauseConnected.WaitOne(WaitOneDuration));
+ });
+ }
+ catch (Exception e)
{
- _options = options;
+ logger.LogError(e.ToString());
+ Assert.Fail(e.ToString());
}
-
- [Fact]
- public async Task Client_Raises_OnConnected_EventArgs()
+ finally
{
- // create one logger per test-method! - cause one file per test-method is generated
- var logger = TestLogHelper.GetLogger();
- var client = GetClient(logger, _options);
- Assert.NotNull(client);
- try
- {
- var pauseConnected = new ManualResetEvent(false);
+ client.Dispose();
+ }
+ }
- await Assert.RaisesAsync(
- h => client.OnConnected += h,
- h => client.OnConnected -= h,
- async () =>
+ [Fact]
+ public async Task Client_Raises_OnDisconnected_EventArgs()
+ {
+ // create one logger per test-method! - cause one file per test-method is generated
+ var logger = TestLogHelper.GetLogger();
+ var client = GetClient(logger, _options);
+ Assert.NotNull(client);
+ try
+ {
+ var pauseDisconnected = new ManualResetEvent(false);
+
+ await Assert.RaisesAsync(
+ h => client.OnDisconnected += h,
+ h => client.OnDisconnected -= h,
+ async () =>
+ {
+ client.OnConnected += async (sender, e) =>
{
- client.OnConnected += async(sender, e) =>
- {
- pauseConnected.Set();
- };
-
- await client.OpenAsync();
- Assert.True(pauseConnected.WaitOne(WaitOneDuration));
- });
- }
- catch (Exception e)
- {
- logger.LogError(e.ToString());
- Assert.Fail(e.ToString());
- }
- finally
- {
- client.Dispose();
- }
- }
+ await client.CloseAsync();
+ };
- [Fact]
- public async Task Client_Raises_OnDisconnected_EventArgs()
+ client.OnDisconnected += async (sender, e) =>
+ {
+ pauseDisconnected.Set();
+ };
+ await client.OpenAsync();
+
+ Assert.True(pauseDisconnected.WaitOne(WaitOneDuration));
+ });
+ }
+ catch (Exception e)
{
- // create one logger per test-method! - cause one file per test-method is generated
- var logger = TestLogHelper.GetLogger();
- var client = GetClient(logger, _options);
- Assert.NotNull(client);
- try
- {
- var pauseDisconnected = new ManualResetEvent(false);
+ logger.LogError(e.ToString());
+ Assert.Fail(e.ToString());
+ }
+ finally
+ {
+ client.Dispose();
+ }
+ }
+
+ [Fact]
+ public async Task Client_Raises_OnReconnected_EventArgs()
+ {
+ // create one logger per test-method! - cause one file per test-method is generated
+ var logger = TestLogHelper.GetLogger();
+ var client = GetClient(logger, _options);
+ Assert.NotNull(client);
+ try
+ {
+ var pauseReconnected = new ManualResetEvent(false);
+
+ await Assert.RaisesAsync(
+ h => client.OnReconnected += h,
+ h => client.OnReconnected -= h,
+ async () =>
+ {
+ client.OnConnected += async (s, e) => await client.ReconnectAsync();
- await Assert.RaisesAsync(
- h => client.OnDisconnected += h,
- h => client.OnDisconnected -= h,
- async () =>
+ client.OnReconnected += async (s, e) =>
{
- client.OnConnected += async (sender, e) =>
- {
- await client.CloseAsync();
- };
-
- client.OnDisconnected += async (sender, e) =>
- {
- pauseDisconnected.Set();
- };
- await client.OpenAsync();
-
- Assert.True(pauseDisconnected.WaitOne(WaitOneDuration));
- });
- }
- catch (Exception e)
- {
- logger.LogError(e.ToString());
- Assert.Fail(e.ToString());
- }
- finally
- {
- client.Dispose();
- }
+ pauseReconnected.Set();
+ };
+ await client.OpenAsync();
+ Assert.True(pauseReconnected.WaitOne(WaitOneDuration));
+ });
}
+ catch (Exception e)
+ {
+ logger.LogError(e.ToString());
+ Assert.Fail(e.ToString());
+ }
+ finally
+ {
+ client.Dispose();
+ }
+ }
- [Fact]
- public async Task Client_Raises_OnReconnected_EventArgs()
+ [Fact]
+ public void Dispose_Client_Before_Connecting_IsOK()
+ {
+ // create one logger per test-method! - cause one file per test-method is generated
+ var logger = TestLogHelper.GetLogger();
+ IClient? client = null;
+ try
{
- // create one logger per test-method! - cause one file per test-method is generated
- var logger = TestLogHelper.GetLogger();
- var client = GetClient(logger, _options);
+ client = GetClient(logger, _options);
Assert.NotNull(client);
- try
- {
- var pauseReconnected = new ManualResetEvent(false);
-
- await Assert.RaisesAsync(
- h => client.OnReconnected += h,
- h => client.OnReconnected -= h,
- async () =>
- {
- client.OnConnected += async (s, e) => await client.ReconnectAsync();
-
- client.OnReconnected += async (s, e) =>
- {
- pauseReconnected.Set();
- };
- await client.OpenAsync();
- Assert.True(pauseReconnected.WaitOne(WaitOneDuration));
- });
- }
- catch (Exception e)
- {
- logger.LogError(e.ToString());
- Assert.Fail(e.ToString());
- }
- finally
- {
- client.Dispose();
- }
+ client.Dispose();
}
-
- [Fact]
- public void Dispose_Client_Before_Connecting_IsOK()
+ catch (Exception e)
{
- // create one logger per test-method! - cause one file per test-method is generated
- var logger = TestLogHelper.GetLogger();
- IClient? client = null;
- try
- {
- client = GetClient(logger, _options);
- Assert.NotNull(client);
- client.Dispose();
- }
- catch (Exception e)
- {
- logger.LogError(e.ToString());
- Assert.Fail(e.ToString());
- }
- finally
- {
- client?.Dispose();
- }
+ logger.LogError(e.ToString());
+ Assert.Fail(e.ToString());
}
-
- private static TClient? GetClient(ILogger logger, IClientOptions? options = null)
+ finally
{
- var constructorParameterTypes = new Type[]
- {
- typeof(IClientOptions),
- typeof(ILogger)
- };
-
- var constructor = typeof(TClient).GetConstructor(constructorParameterTypes);
- var constructorParameters = new object[]
- {
- options ?? new ClientOptions(),
- logger
- };
-
- return (TClient?)constructor?.Invoke(constructorParameters);
+ client?.Dispose();
}
}
+ private static TClient? GetClient(ILogger logger, IClientOptions? options = null)
+ {
+ var constructorParameterTypes = new Type[]
+ {
+ typeof(IClientOptions),
+ typeof(ILogger)
+ };
+
+ var constructor = typeof(TClient).GetConstructor(constructorParameterTypes);
+ var constructorParameters = new object[]
+ {
+ options ?? new ClientOptions(),
+ logger
+ };
+
+ return (TClient?)constructor?.Invoke(constructorParameters);
+ }
+}
+
- #region Modified Assert
- //TL;DR: Extracted version of XUNIT with
- //modification to accept new event Handler
+#region Modified Assert
+//TL;DR: Extracted version of XUNIT with
+//modification to accept new event Handler
- public partial class Assert
- {
+public partial class Assert
+{
- ///
- /// Verifies that a event with the exact event args (and not a derived type) is raised.
- ///
- /// The type of the event arguments to expect
- /// Code to attach the event handler
- /// Code to detach the event handler
- /// A delegate to the code to be tested
- /// The event sender and arguments wrapped in an object
- /// Thrown when the expected event was not raised.
- public static async Task> RaisesAsync(Action> attach, Action> detach, Func testCode)
- {
- var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);
+ ///
+ /// Verifies that a event with the exact event args (and not a derived type) is raised.
+ ///
+ /// The type of the event arguments to expect
+ /// Code to attach the event handler
+ /// Code to detach the event handler
+ /// A delegate to the code to be tested
+ /// The event sender and arguments wrapped in an object
+ /// Thrown when the expected event was not raised.
+ public static async Task> RaisesAsync(Action> attach, Action> detach, Func testCode)
+ {
+ var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);
- if (raisedEvent == null)
- throw new RaisesException(typeof(T));
+ if (raisedEvent == null)
+ throw new RaisesException(typeof(T));
- if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T)))
- throw new RaisesException(typeof(T), raisedEvent.Arguments.GetType());
+ if (raisedEvent.Arguments != null && !raisedEvent.Arguments.GetType().Equals(typeof(T)))
+ throw new RaisesException(typeof(T), raisedEvent.Arguments.GetType());
- return raisedEvent;
- }
+ return raisedEvent;
+ }
- ///
- /// Verifies that an event with the exact or a derived event args is raised.
- ///
- /// The type of the event arguments to expect
- /// Code to attach the event handler
- /// Code to detach the event handler
- /// A delegate to the code to be tested
- /// The event sender and arguments wrapped in an object
- /// Thrown when the expected event was not raised.
- public static async Task> RaisesAnyAsync(Action> attach, Action> detach, Func testCode)
- {
- var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);
+ ///
+ /// Verifies that an event with the exact or a derived event args is raised.
+ ///
+ /// The type of the event arguments to expect
+ /// Code to attach the event handler
+ /// Code to detach the event handler
+ /// A delegate to the code to be tested
+ /// The event sender and arguments wrapped in an object
+ /// Thrown when the expected event was not raised.
+ public static async Task> RaisesAnyAsync(Action> attach, Action> detach, Func testCode)
+ {
+ var raisedEvent = await RaisesAsyncInternal(attach, detach, testCode);
- if (raisedEvent == null)
- throw new RaisesException(typeof(T));
+ if (raisedEvent == null)
+ throw new RaisesException(typeof(T));
- return raisedEvent;
- }
+ return raisedEvent;
+ }
#if XUNIT_NULLABLE
static async Task?> RaisesAsyncInternal(Action> attach, Action> detach, Func testCode)
#else
- static async Task> RaisesAsyncInternal(Action> attach, Action> detach, Func testCode)
+ static async Task> RaisesAsyncInternal(Action> attach, Action> detach, Func testCode)
#endif
- {
- NotNull(attach);
- NotNull(detach);
- NotNull(testCode);
+ {
+ NotNull(attach);
+ NotNull(detach);
+ NotNull(testCode);
#if XUNIT_NULLABLE
RaisedEvent? raisedEvent = null;
void handler(object? s, T args) => raisedEvent = new RaisedEvent(s, args);
#else
- RaisedEvent raisedEvent = null;
- AsyncEventHandler value = (object s, T args) =>
- {
- raisedEvent = new RaisedEvent(s, args);
- return Task.CompletedTask;
- };
- AsyncEventHandler handler = value;
+ RaisedEvent? raisedEvent = null;
+ AsyncEventHandler value = (object? s, T args) =>
+ {
+ raisedEvent = new RaisedEvent(s, args);
+ return Task.CompletedTask;
+ };
+ AsyncEventHandler handler = value;
#endif
- attach(handler);
- await testCode();
- detach(handler);
- return raisedEvent;
- }
+ attach(handler);
+ await testCode();
+ detach(handler);
+ return raisedEvent;
+ }
+ ///
+ /// Represents a raised event after the fact.
+ ///
+ /// The type of the event arguments.
+ public class RaisedEvent
+ {
///
- /// Represents a raised event after the fact.
+ /// The sender of the event.
///
- /// The type of the event arguments.
- public class RaisedEvent
- {
- ///
- /// The sender of the event.
- ///
#if XUNIT_NULLABLE
public object? Sender { get; }
#else
- public object Sender { get; }
+ public object Sender { get; }
#endif
- ///
- /// The event arguments.
- ///
- public T Arguments { get; }
+ ///
+ /// The event arguments.
+ ///
+ public T Arguments { get; }
- ///
- /// Creates a new instance of the class.
- ///
- /// The sender of the event.
- /// The event arguments
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The sender of the event.
+ /// The event arguments
#if XUNIT_NULLABLE
public RaisedEvent(object? sender, T args)
#else
- public RaisedEvent(object sender, T args)
+ public RaisedEvent(object sender, T args)
#endif
- {
- Sender = sender;
- Arguments = args;
- }
+ {
+ Sender = sender;
+ Arguments = args;
}
+ }
#if XUNIT_NULLABLE
public static void False([DoesNotReturnIf(parameterValue: true)] bool condition)
#else
- public static void False(bool condition)
+ public static void False(bool condition)
#endif
- {
- False((bool?)condition, null);
- }
+ {
+ False((bool?)condition, null);
+ }
- ///
- /// Verifies that the condition is false.
- ///
- /// The condition to be tested
- /// Thrown if the condition is not false
+ ///
+ /// Verifies that the condition is false.
+ ///
+ /// The condition to be tested
+ /// Thrown if the condition is not false
#if XUNIT_NULLABLE
public static void False([DoesNotReturnIf(parameterValue: true)] bool? condition)
#else
- public static void False(bool? condition)
+ public static void False(bool? condition)
#endif
- {
- False(condition, null);
- }
+ {
+ False(condition, null);
+ }
- ///
- /// Verifies that the condition is false.
- ///
- /// The condition to be tested
- /// The message to show when the condition is not false
- /// Thrown if the condition is not false
+ ///
+ /// Verifies that the condition is false.
+ ///
+ /// The condition to be tested
+ /// The message to show when the condition is not false
+ /// Thrown if the condition is not false
#if XUNIT_NULLABLE
public static void False([DoesNotReturnIf(parameterValue: true)] bool condition, string? userMessage)
#else
- public static void False(bool condition, string userMessage)
+ public static void False(bool condition, string userMessage)
#endif
- {
- False((bool?)condition, userMessage);
- }
+ {
+ False((bool?)condition, userMessage);
+ }
- ///
- /// Verifies that the condition is false.
- ///
- /// The condition to be tested
- /// The message to show when the condition is not false
- /// Thrown if the condition is not false
+ ///
+ /// Verifies that the condition is false.
+ ///
+ /// The condition to be tested
+ /// The message to show when the condition is not false
+ /// Thrown if the condition is not false
#if XUNIT_NULLABLE
public static void False([DoesNotReturnIf(parameterValue: true)] bool? condition, string? userMessage)
#else
- public static void False(bool? condition, string userMessage)
+ public static void False(bool? condition, string userMessage)
#endif
- {
- if (!condition.HasValue || condition.GetValueOrDefault())
- throw new FalseException(userMessage, condition);
- }
+ {
+ if (!condition.HasValue || condition.GetValueOrDefault())
+ throw new FalseException(userMessage, condition);
+ }
- ///
- /// Verifies that an expression is true.
- ///
- /// The condition to be inspected
- /// Thrown when the condition is false
+ ///
+ /// Verifies that an expression is true.
+ ///
+ /// The condition to be inspected
+ /// Thrown when the condition is false
#if XUNIT_NULLABLE
public static void True([DoesNotReturnIf(parameterValue: false)] bool condition)
#else
- public static void True(bool condition)
+ public static void True(bool condition)
#endif
- {
- True((bool?)condition, null);
- }
+ {
+ True((bool?)condition, null);
+ }
- ///
- /// Verifies that an expression is true.
- ///
- /// The condition to be inspected
- /// Thrown when the condition is false
+ ///
+ /// Verifies that an expression is true.
+ ///
+ /// The condition to be inspected
+ /// Thrown when the condition is false
#if XUNIT_NULLABLE
public static void True([DoesNotReturnIf(parameterValue: false)] bool? condition)
#else
- public static void True(bool? condition)
+ public static void True(bool? condition)
#endif
- {
- True(condition, null);
- }
+ {
+ True(condition, null);
+ }
- ///
- /// Verifies that an expression is true.
- ///
- /// The condition to be inspected
- /// The message to be shown when the condition is false
- /// Thrown when the condition is false
+ ///
+ /// Verifies that an expression is true.
+ ///
+ /// The condition to be inspected
+ /// The message to be shown when the condition is false
+ /// Thrown when the condition is false
#if XUNIT_NULLABLE
public static void True([DoesNotReturnIf(parameterValue: false)] bool condition, string? userMessage)
#else
- public static void True(bool condition, string userMessage)
+ public static void True(bool condition, string userMessage)
#endif
- {
- True((bool?)condition, userMessage);
- }
+ {
+ True((bool?)condition, userMessage);
+ }
- ///
- /// Verifies that an expression is true.
- ///
- /// The condition to be inspected
- /// The message to be shown when the condition is false
- /// Thrown when the condition is false
+ ///
+ /// Verifies that an expression is true.
+ ///
+ /// The condition to be inspected
+ /// The message to be shown when the condition is false
+ /// Thrown when the condition is false
#if XUNIT_NULLABLE
public static void True([DoesNotReturnIf(parameterValue: false)] bool? condition, string? userMessage)
#else
- public static void True(bool? condition, string userMessage)
+ public static void True(bool? condition, string userMessage)
#endif
- {
- if (!condition.HasValue || !condition.GetValueOrDefault())
- throw new TrueException(userMessage, condition);
- }
+ {
+ if (!condition.HasValue || !condition.GetValueOrDefault())
+ throw new TrueException(userMessage, condition);
+ }
- ///
- /// Verifies that a string contains a given sub-string, using the current culture.
- ///
- /// The sub-string expected to be in the string
- /// The string to be inspected
- /// Thrown when the sub-string is not present inside the string
+ ///
+ /// Verifies that a string contains a given sub-string, using the current culture.
+ ///
+ /// The sub-string expected to be in the string
+ /// The string to be inspected
+ /// Thrown when the sub-string is not present inside the string
#if XUNIT_NULLABLE
public static void Contains(string expectedSubstring, string? actualString)
#else
- public static void Contains(string expectedSubstring, string actualString)
+ public static void Contains(string expectedSubstring, string actualString)
#endif
- {
- Contains(expectedSubstring, actualString, StringComparison.CurrentCulture);
- }
+ {
+ Contains(expectedSubstring, actualString, StringComparison.CurrentCulture);
+ }
- ///
- /// Verifies that a string contains a given sub-string, using the given comparison type.
- ///
- /// The sub-string expected to be in the string
- /// The string to be inspected
- /// The type of string comparison to perform
- /// Thrown when the sub-string is not present inside the string
+ ///
+ /// Verifies that a string contains a given sub-string, using the given comparison type.
+ ///
+ /// The sub-string expected to be in the string
+ /// The string to be inspected
+ /// The type of string comparison to perform
+ /// Thrown when the sub-string is not present inside the string
#if XUNIT_NULLABLE
public static void Contains(string expectedSubstring, string? actualString, StringComparison comparisonType)
#else
- public static void Contains(string expectedSubstring, string actualString, StringComparison comparisonType)
+ public static void Contains(string expectedSubstring, string actualString, StringComparison comparisonType)
#endif
- {
- NotNull(expectedSubstring);
+ {
+ NotNull(expectedSubstring);
- if (actualString == null || actualString.IndexOf(expectedSubstring, comparisonType) < 0)
- throw new ContainsException(expectedSubstring, actualString);
- }
+ if (actualString == null || actualString.IndexOf(expectedSubstring, comparisonType) < 0)
+ throw new ContainsException(expectedSubstring, actualString);
+ }
- ///
- /// Verifies that a string does not contain a given sub-string, using the current culture.
- ///
- /// The sub-string which is expected not to be in the string
- /// The string to be inspected
- /// Thrown when the sub-string is present inside the string
+ ///
+ /// Verifies that a string does not contain a given sub-string, using the current culture.
+ ///
+ /// The sub-string which is expected not to be in the string
+ /// The string to be inspected
+ /// Thrown when the sub-string is present inside the string
#if XUNIT_NULLABLE
public static void DoesNotContain(string expectedSubstring, string? actualString)
#else
- public static void DoesNotContain(string expectedSubstring, string actualString)
+ public static void DoesNotContain(string expectedSubstring, string actualString)
#endif
- {
- DoesNotContain(expectedSubstring, actualString, StringComparison.CurrentCulture);
- }
+ {
+ DoesNotContain(expectedSubstring, actualString, StringComparison.CurrentCulture);
+ }
- ///
- /// Verifies that a string does not contain a given sub-string, using the current culture.
- ///
- /// The sub-string which is expected not to be in the string
- /// The string to be inspected
- /// The type of string comparison to perform
- /// Thrown when the sub-string is present inside the given string
+ ///
+ /// Verifies that a string does not contain a given sub-string, using the current culture.
+ ///
+ /// The sub-string which is expected not to be in the string
+ /// The string to be inspected
+ /// The type of string comparison to perform
+ /// Thrown when the sub-string is present inside the given string
#if XUNIT_NULLABLE
public static void DoesNotContain(string expectedSubstring, string? actualString, StringComparison comparisonType)
#else
- public static void DoesNotContain(string expectedSubstring, string actualString, StringComparison comparisonType)
+ public static void DoesNotContain(string expectedSubstring, string actualString, StringComparison comparisonType)
#endif
- {
- NotNull(expectedSubstring);
+ {
+ NotNull(expectedSubstring);
- if (actualString != null && actualString.IndexOf(expectedSubstring, comparisonType) >= 0)
- throw new DoesNotContainException(expectedSubstring, actualString);
- }
+ if (actualString != null && actualString.IndexOf(expectedSubstring, comparisonType) >= 0)
+ throw new DoesNotContainException(expectedSubstring, actualString);
+ }
- ///
- /// Verifies that a string starts with a given string, using the current culture.
- ///
- /// The string expected to be at the start of the string
- /// The string to be inspected
- /// Thrown when the string does not start with the expected string
+ ///
+ /// Verifies that a string starts with a given string, using the current culture.
+ ///
+ /// The string expected to be at the start of the string
+ /// The string to be inspected
+ /// Thrown when the string does not start with the expected string
#if XUNIT_NULLABLE
public static void StartsWith(string? expectedStartString, string? actualString)
#else
- public static void StartsWith(string expectedStartString, string actualString)
+ public static void StartsWith(string expectedStartString, string actualString)
#endif
- {
- StartsWith(expectedStartString, actualString, StringComparison.CurrentCulture);
- }
+ {
+ StartsWith(expectedStartString, actualString, StringComparison.CurrentCulture);
+ }
- ///
- /// Verifies that a string starts with a given string, using the given comparison type.
- ///
- /// The string expected to be at the start of the string
- /// The string to be inspected
- /// The type of string comparison to perform
- /// Thrown when the string does not start with the expected string
+ ///
+ /// Verifies that a string starts with a given string, using the given comparison type.
+ ///
+ /// The string expected to be at the start of the string
+ /// The string to be inspected
+ /// The type of string comparison to perform
+ /// Thrown when the string does not start with the expected string
#if XUNIT_NULLABLE
public static void StartsWith(string? expectedStartString, string? actualString, StringComparison comparisonType)
#else
- public static void StartsWith(string expectedStartString, string actualString, StringComparison comparisonType)
+ public static void StartsWith(string expectedStartString, string actualString, StringComparison comparisonType)
#endif
- {
- if (expectedStartString == null || actualString == null || !actualString.StartsWith(expectedStartString, comparisonType))
- throw new StartsWithException(expectedStartString, actualString);
- }
+ {
+ if (expectedStartString == null || actualString == null || !actualString.StartsWith(expectedStartString, comparisonType))
+ throw new StartsWithException(expectedStartString, actualString);
+ }
- ///
- /// Verifies that a string ends with a given string, using the current culture.
- ///
- /// The string expected to be at the end of the string
- /// The string to be inspected
- /// Thrown when the string does not end with the expected string
+ ///
+ /// Verifies that a string ends with a given string, using the current culture.
+ ///
+ /// The string expected to be at the end of the string
+ /// The string to be inspected
+ /// Thrown when the string does not end with the expected string
#if XUNIT_NULLABLE
public static void EndsWith(string? expectedEndString, string? actualString)
#else
- public static void EndsWith(string expectedEndString, string actualString)
+ public static void EndsWith(string expectedEndString, string actualString)
#endif
- {
- EndsWith(expectedEndString, actualString, StringComparison.CurrentCulture);
- }
+ {
+ EndsWith(expectedEndString, actualString, StringComparison.CurrentCulture);
+ }
- ///
- /// Verifies that a string ends with a given string, using the given comparison type.
- ///
- /// The string expected to be at the end of the string
- /// The string to be inspected
- /// The type of string comparison to perform
- /// Thrown when the string does not end with the expected string
+ ///
+ /// Verifies that a string ends with a given string, using the given comparison type.
+ ///
+ /// The string expected to be at the end of the string
+ /// The string to be inspected
+ /// The type of string comparison to perform
+ /// Thrown when the string does not end with the expected string
#if XUNIT_NULLABLE
public static void EndsWith(string? expectedEndString, string? actualString, StringComparison comparisonType)
#else
- public static void EndsWith(string expectedEndString, string actualString, StringComparison comparisonType)
+ public static void EndsWith(string expectedEndString, string actualString, StringComparison comparisonType)
#endif
- {
- if (expectedEndString == null || actualString == null || !actualString.EndsWith(expectedEndString, comparisonType))
- throw new EndsWithException(expectedEndString, actualString);
- }
+ {
+ if (expectedEndString == null || actualString == null || !actualString.EndsWith(expectedEndString, comparisonType))
+ throw new EndsWithException(expectedEndString, actualString);
+ }
- ///
- /// Verifies that a string matches a regular expression.
- ///
- /// The regex pattern expected to match
- /// The string to be inspected
- /// Thrown when the string does not match the regex pattern
+ ///
+ /// Verifies that a string matches a regular expression.
+ ///
+ /// The regex pattern expected to match
+ /// The string to be inspected
+ /// Thrown when the string does not match the regex pattern
#if XUNIT_NULLABLE
public static void Matches(string expectedRegexPattern, string? actualString)
#else
- public static void Matches(string expectedRegexPattern, string actualString)
+ public static void Matches(string expectedRegexPattern, string actualString)
#endif
- {
- NotNull(expectedRegexPattern);
+ {
+ NotNull(expectedRegexPattern);
- if (actualString == null || !Regex.IsMatch(actualString, expectedRegexPattern))
- throw new MatchesException(expectedRegexPattern, actualString);
- }
+ if (actualString == null || !Regex.IsMatch(actualString, expectedRegexPattern))
+ throw new MatchesException(expectedRegexPattern, actualString);
+ }
- ///
- /// Verifies that a string matches a regular expression.
- ///
- /// The regex expected to match
- /// The string to be inspected
- /// Thrown when the string does not match the regex
+ ///
+ /// Verifies that a string matches a regular expression.
+ ///
+ /// The regex expected to match
+ /// The string to be inspected
+ /// Thrown when the string does not match the regex
#if XUNIT_NULLABLE
public static void Matches(Regex expectedRegex, string? actualString)
#else
- public static void Matches(Regex expectedRegex, string actualString)
+ public static void Matches(Regex expectedRegex, string actualString)
#endif
- {
- NotNull(expectedRegex);
+ {
+ NotNull(expectedRegex);
- if (actualString == null || !expectedRegex.IsMatch(actualString))
- throw new MatchesException(expectedRegex.ToString(), actualString);
- }
+ if (actualString == null || !expectedRegex.IsMatch(actualString))
+ throw new MatchesException(expectedRegex.ToString(), actualString);
+ }
- ///
- /// Verifies that a string does not match a regular expression.
- ///
- /// The regex pattern expected not to match
- /// The string to be inspected
- /// Thrown when the string matches the regex pattern
+ ///
+ /// Verifies that a string does not match a regular expression.
+ ///
+ /// The regex pattern expected not to match
+ /// The string to be inspected
+ /// Thrown when the string matches the regex pattern
#if XUNIT_NULLABLE
public static void DoesNotMatch(string expectedRegexPattern, string? actualString)
#else
- public static void DoesNotMatch(string expectedRegexPattern, string actualString)
+ public static void DoesNotMatch(string expectedRegexPattern, string actualString)
#endif
- {
- NotNull(expectedRegexPattern);
+ {
+ NotNull(expectedRegexPattern);
- if (actualString != null && Regex.IsMatch(actualString, expectedRegexPattern))
- throw new DoesNotMatchException(expectedRegexPattern, actualString);
- }
+ if (actualString != null && Regex.IsMatch(actualString, expectedRegexPattern))
+ throw new DoesNotMatchException(expectedRegexPattern, actualString);
+ }
- ///
- /// Verifies that a string does not match a regular expression.
- ///
- /// The regex expected not to match
- /// The string to be inspected
- /// Thrown when the string matches the regex
+ ///
+ /// Verifies that a string does not match a regular expression.
+ ///
+ /// The regex expected not to match
+ /// The string to be inspected
+ /// Thrown when the string matches the regex
#if XUNIT_NULLABLE
public static void DoesNotMatch(Regex expectedRegex, string? actualString)
#else
- public static void DoesNotMatch(Regex expectedRegex, string actualString)
+ public static void DoesNotMatch(Regex expectedRegex, string actualString)
#endif
- {
- NotNull(expectedRegex);
+ {
+ NotNull(expectedRegex);
- if (actualString != null && expectedRegex.IsMatch(actualString))
- throw new DoesNotMatchException(expectedRegex.ToString(), actualString);
- }
+ if (actualString != null && expectedRegex.IsMatch(actualString))
+ throw new DoesNotMatchException(expectedRegex.ToString(), actualString);
+ }
- ///
- /// Verifies that two strings are equivalent.
- ///
- /// The expected string value.
- /// The actual string value.
- /// Thrown when the strings are not equivalent.
+ ///
+ /// Verifies that two strings are equivalent.
+ ///
+ /// The expected string value.
+ /// The actual string value.
+ /// Thrown when the strings are not equivalent.
#if XUNIT_NULLABLE
public static void Equal(string? expected, string? actual)
#else
- public static void Equal(string expected, string actual)
+ public static void Equal(string expected, string actual)
#endif
- {
- Equal(expected, actual, false, false, false);
- }
+ {
+ Equal(expected, actual, false, false, false);
+ }
- ///
- /// Verifies that two strings are equivalent.
- ///
- /// The expected string value.
- /// The actual string value.
- /// If set to true, ignores cases differences. The invariant culture is used.
- /// If set to true, treats \r\n, \r, and \n as equivalent.
- /// If set to true, treats spaces and tabs (in any non-zero quantity) as equivalent.
- /// Thrown when the strings are not equivalent.
+ ///
+ /// Verifies that two strings are equivalent.
+ ///
+ /// The expected string value.
+ /// The actual string value.
+ /// If set to true, ignores cases differences. The invariant culture is used.
+ /// If set to true, treats \r\n, \r, and \n as equivalent.
+ /// If set to true, treats spaces and tabs (in any non-zero quantity) as equivalent.
+ /// Thrown when the strings are not equivalent.
#if XUNIT_NULLABLE
public static void Equal(
string? expected,
@@ -653,14 +653,14 @@ public static void Equal(
bool ignoreLineEndingDifferences = false,
bool ignoreWhiteSpaceDifferences = false)
#else
- public static void Equal(
- string expected,
- string actual,
- bool ignoreCase = false,
- bool ignoreLineEndingDifferences = false,
- bool ignoreWhiteSpaceDifferences = false)
+ public static void Equal(
+ string expected,
+ string actual,
+ bool ignoreCase = false,
+ bool ignoreLineEndingDifferences = false,
+ bool ignoreWhiteSpaceDifferences = false)
#endif
- {
+ {
#if XUNIT_SPAN
if (expected == null && actual == null)
return;
@@ -669,162 +669,151 @@ public static void Equal(
Equal(expected.AsSpan(), actual.AsSpan(), ignoreCase, ignoreLineEndingDifferences, ignoreWhiteSpaceDifferences);
#else
- // Start out assuming the one of the values is null
- int expectedIndex = -1;
- int actualIndex = -1;
- int expectedLength = 0;
- int actualLength = 0;
+ // Start out assuming the one of the values is null
+ int expectedIndex = -1;
+ int actualIndex = -1;
+ int expectedLength = 0;
+ int actualLength = 0;
- if (expected == null)
- {
- if (actual == null)
- return;
- }
- else if (actual != null)
+ if (expected == null)
+ {
+ if (actual == null)
+ return;
+ }
+ else if (actual != null)
+ {
+ // Walk the string, keeping separate indices since we can skip variable amounts of
+ // data based on ignoreLineEndingDifferences and ignoreWhiteSpaceDifferences.
+ expectedIndex = 0;
+ actualIndex = 0;
+ expectedLength = expected.Length;
+ actualLength = actual.Length;
+
+ while (expectedIndex < expectedLength && actualIndex < actualLength)
{
- // Walk the string, keeping separate indices since we can skip variable amounts of
- // data based on ignoreLineEndingDifferences and ignoreWhiteSpaceDifferences.
- expectedIndex = 0;
- actualIndex = 0;
- expectedLength = expected.Length;
- actualLength = actual.Length;
-
- while (expectedIndex < expectedLength && actualIndex < actualLength)
- {
- char expectedChar = expected[expectedIndex];
- char actualChar = actual[actualIndex];
+ char expectedChar = expected[expectedIndex];
+ char actualChar = actual[actualIndex];
- if (ignoreLineEndingDifferences && IsLineEnding(expectedChar) && IsLineEnding(actualChar))
- {
- expectedIndex = SkipLineEnding(expected, expectedIndex);
- actualIndex = SkipLineEnding(actual, actualIndex);
- }
- else if (ignoreWhiteSpaceDifferences && IsWhiteSpace(expectedChar) && IsWhiteSpace(actualChar))
+ if (ignoreLineEndingDifferences && IsLineEnding(expectedChar) && IsLineEnding(actualChar))
+ {
+ expectedIndex = SkipLineEnding(expected, expectedIndex);
+ actualIndex = SkipLineEnding(actual, actualIndex);
+ }
+ else if (ignoreWhiteSpaceDifferences && IsWhiteSpace(expectedChar) && IsWhiteSpace(actualChar))
+ {
+ expectedIndex = SkipWhitespace(expected, expectedIndex);
+ actualIndex = SkipWhitespace(actual, actualIndex);
+ }
+ else
+ {
+ if (ignoreCase)
{
- expectedIndex = SkipWhitespace(expected, expectedIndex);
- actualIndex = SkipWhitespace(actual, actualIndex);
+ expectedChar = Char.ToUpperInvariant(expectedChar);
+ actualChar = Char.ToUpperInvariant(actualChar);
}
- else
+
+ if (expectedChar != actualChar)
{
- if (ignoreCase)
- {
- expectedChar = Char.ToUpperInvariant(expectedChar);
- actualChar = Char.ToUpperInvariant(actualChar);
- }
-
- if (expectedChar != actualChar)
- {
- break;
- }
-
- expectedIndex++;
- actualIndex++;
+ break;
}
- }
- }
- if (expectedIndex < expectedLength || actualIndex < actualLength)
- {
- throw new EqualException(expected, actual, expectedIndex, actualIndex);
+ expectedIndex++;
+ actualIndex++;
+ }
}
-#endif
}
- static bool IsLineEnding(char c)
+
+ if (expectedIndex < expectedLength || actualIndex < actualLength)
{
- return c == '\r' || c == '\n';
+ throw new EqualException(expected, actual, expectedIndex, actualIndex);
}
+#endif
+ }
+ static bool IsLineEnding(char c)
+ {
+ return c == '\r' || c == '\n';
+ }
+
+ static bool IsWhiteSpace(char c)
+ {
+ return c == ' ' || c == '\t';
+ }
- static bool IsWhiteSpace(char c)
+ static int SkipLineEnding(string value, int index)
+ {
+ if (value[index] == '\r')
{
- return c == ' ' || c == '\t';
+ ++index;
}
-
- static int SkipLineEnding(string value, int index)
+ if (index < value.Length && value[index] == '\n')
{
- if (value[index] == '\r')
- {
- ++index;
- }
- if (index < value.Length && value[index] == '\n')
- {
- ++index;
- }
-
- return index;
+ ++index;
}
- static int SkipWhitespace(string value, int index)
+ return index;
+ }
+
+ static int SkipWhitespace(string value, int index)
+ {
+ while (index < value.Length)
{
- while (index < value.Length)
+ switch (value[index])
{
- switch (value[index])
- {
- case ' ':
- case '\t':
- index++;
- break;
+ case ' ':
+ case '\t':
+ index++;
+ break;
- default:
- return index;
- }
+ default:
+ return index;
}
-
- return index;
}
- ///
- /// Verifies that an object reference is not null.
- ///
- /// The object to be validated
- /// Thrown when the object reference is null
+ return index;
+ }
+
+ ///
+ /// Verifies that an object reference is not null.
+ ///
+ /// The object to be validated
+ /// Thrown when the object reference is null
#if XUNIT_NULLABLE
public static void NotNull([NotNull] object? @object)
#else
- public static void NotNull(object @object)
+ public static void NotNull(object @object)
#endif
- {
- if (@object == null)
- throw new NotNullException();
- }
+ {
+ if (@object == null)
+ throw new NotNullException();
+ }
- ///
- /// Verifies that an object reference is null.
- ///
- /// The object to be inspected
- /// Thrown when the object reference is not null
+ ///
+ /// Verifies that an object reference is null.
+ ///
+ /// The object to be inspected
+ /// Thrown when the object reference is not null
#if XUNIT_NULLABLE
public static void Null([MaybeNull] object? @object)
#else
- public static void Null(object @object)
+ public static void Null(object @object)
#endif
- {
- if (@object != null)
- throw new NullException(@object);
- }
+ {
+ if (@object != null)
+ throw new NullException(@object);
+ }
- ///
- /// Indicates that the test should immediately fail.
- ///
- /// The failure message
+ ///
+ /// Indicates that the test should immediately fail.
+ ///
+ /// The failure message
#if XUNIT_NULLABLE
[DoesNotReturn]
#endif
- public static void Fail(string message)
- {
- NotNull( message);
-
- throw new FailException(message);
- }
-
-
-
+ public static void Fail(string message)
+ {
+ NotNull( message);
+ throw new FailException(message);
}
- #endregion
-
-
}
-
-
-
-
+#endregion
diff --git a/src/TwitchLib.Communication.Tests/Clients/TcpClientTests.cs b/src/TwitchLib.Communication.Tests/Clients/TcpClientTests.cs
index 007f058..439e5d9 100644
--- a/src/TwitchLib.Communication.Tests/Clients/TcpClientTests.cs
+++ b/src/TwitchLib.Communication.Tests/Clients/TcpClientTests.cs
@@ -1,10 +1,9 @@
using TwitchLib.Communication.Clients;
using TwitchLib.Communication.Models;
-namespace TwitchLib.Communication.Tests.Clients
+namespace TwitchLib.Communication.Tests.Clients;
+
+public class TcpClientTests : ClientTestsBase
{
- public class TcpClientTests : ClientTestsBase
- {
- public TcpClientTests() : base(new ClientOptions(useSsl: false)) { }
- }
+ public TcpClientTests() : base(new ClientOptions(useSsl: false)) { }
}
\ No newline at end of file
diff --git a/src/TwitchLib.Communication.Tests/Clients/WebSocketClientTests.cs b/src/TwitchLib.Communication.Tests/Clients/WebSocketClientTests.cs
index fe9a507..0009ded 100644
--- a/src/TwitchLib.Communication.Tests/Clients/WebSocketClientTests.cs
+++ b/src/TwitchLib.Communication.Tests/Clients/WebSocketClientTests.cs
@@ -1,8 +1,7 @@
using TwitchLib.Communication.Clients;
-namespace TwitchLib.Communication.Tests.Clients
+namespace TwitchLib.Communication.Tests.Clients;
+
+public class WebSocketClientTests : ClientTestsBase
{
- public class WebSocketClientTests : ClientTestsBase
- {
- }
-}
\ No newline at end of file
+}
diff --git a/src/TwitchLib.Communication.Tests/Helpers/TestLogHelper.cs b/src/TwitchLib.Communication.Tests/Helpers/TestLogHelper.cs
index ce5f44a..900e9a9 100644
--- a/src/TwitchLib.Communication.Tests/Helpers/TestLogHelper.cs
+++ b/src/TwitchLib.Communication.Tests/Helpers/TestLogHelper.cs
@@ -5,65 +5,64 @@
using Serilog.Events;
using Serilog.Exceptions;
-namespace TwitchLib.Communication.Tests.Helpers
+namespace TwitchLib.Communication.Tests.Helpers;
+
+internal static class TestLogHelper
{
- internal static class TestLogHelper
- {
- private static readonly string OUTPUT_TEMPLATE =
- "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u}] {Message:lj}{NewLine}{Exception}{NewLine}";
+ private static readonly string OUTPUT_TEMPLATE =
+ "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u}] {Message:lj}{NewLine}{Exception}{NewLine}";
- private static readonly string NEW_TEST_RUN_INDICATOR;
+ private static readonly string NEW_TEST_RUN_INDICATOR;
- static TestLogHelper()
- {
- StringBuilder builder = new StringBuilder();
- builder.AppendLine();
- builder.AppendLine(new string('-', 80));
- builder.Append(new string(' ', 34));
- builder.AppendLine("new Test-Run");
- builder.AppendLine(new string('-', 80));
- NEW_TEST_RUN_INDICATOR = builder.ToString();
- }
+ static TestLogHelper()
+ {
+ var builder = new StringBuilder();
+ builder.AppendLine();
+ builder.Append('-', 80).AppendLine();
+ builder.Append(' ', 34);
+ builder.AppendLine("new Test-Run");
+ builder.Append('-', 80).AppendLine();
+ NEW_TEST_RUN_INDICATOR = builder.ToString();
+ }
- internal static Microsoft.Extensions.Logging.ILogger GetLogger(
- LogEventLevel logEventLevel = LogEventLevel.Verbose,
- [CallerMemberName] string callerMemberName = "TestMethod")
- {
- Serilog.ILogger logger = GetSerilogLogger(typeof(T).Name,
- callerMemberName,
- logEventLevel);
- Microsoft.Extensions.Logging.ILoggerFactory loggerFactory =
- new Serilog.Extensions.Logging.SerilogLoggerFactory(logger);
- return loggerFactory.CreateLogger();
- }
+ internal static Microsoft.Extensions.Logging.ILogger GetLogger(
+ LogEventLevel logEventLevel = LogEventLevel.Verbose,
+ [CallerMemberName] string callerMemberName = "TestMethod")
+ {
+ Serilog.ILogger logger = GetSerilogLogger(typeof(T).Name,
+ callerMemberName,
+ logEventLevel);
+ Microsoft.Extensions.Logging.ILoggerFactory loggerFactory =
+ new Serilog.Extensions.Logging.SerilogLoggerFactory(logger);
+ return loggerFactory.CreateLogger();
+ }
- private static Serilog.ILogger GetSerilogLogger(string typeName,
- string callerMemberName,
- LogEventLevel logEventLevel)
- {
- Serilog.LoggerConfiguration loggerConfiguration = GetConfiguration(typeName,
- callerMemberName,
- logEventLevel);
- Serilog.ILogger logger = loggerConfiguration.CreateLogger().ForContext();
- logger.Information(NEW_TEST_RUN_INDICATOR);
- return logger;
- }
+ private static Serilog.ILogger GetSerilogLogger(string typeName,
+ string callerMemberName,
+ LogEventLevel logEventLevel)
+ {
+ Serilog.LoggerConfiguration loggerConfiguration = GetConfiguration(typeName,
+ callerMemberName,
+ logEventLevel);
+ Serilog.ILogger logger = loggerConfiguration.CreateLogger().ForContext();
+ logger.Information(NEW_TEST_RUN_INDICATOR);
+ return logger;
+ }
- private static Serilog.LoggerConfiguration GetConfiguration(string typeName,
- string callerMemberName,
- LogEventLevel logEventLevel)
- {
- Serilog.LoggerConfiguration loggerConfiguration = new Serilog.LoggerConfiguration();
- loggerConfiguration.MinimumLevel.Verbose();
- string path = $"../../../Logs/{typeName}/{callerMemberName}.log";
- loggerConfiguration.WriteTo.File(
- path: path,
- restrictedToMinimumLevel: logEventLevel,
- outputTemplate: OUTPUT_TEMPLATE
- );
- loggerConfiguration.Enrich.WithExceptionDetails();
- loggerConfiguration.Enrich.FromLogContext();
- return loggerConfiguration;
- }
+ private static Serilog.LoggerConfiguration GetConfiguration(string typeName,
+ string callerMemberName,
+ LogEventLevel logEventLevel)
+ {
+ var loggerConfiguration = new Serilog.LoggerConfiguration();
+ loggerConfiguration.MinimumLevel.Verbose();
+ string path = $"../../../Logs/{typeName}/{callerMemberName}.log";
+ loggerConfiguration.WriteTo.File(
+ path: path,
+ restrictedToMinimumLevel: logEventLevel,
+ outputTemplate: OUTPUT_TEMPLATE
+ );
+ loggerConfiguration.Enrich.WithExceptionDetails();
+ loggerConfiguration.Enrich.FromLogContext();
+ return loggerConfiguration;
}
-}
\ No newline at end of file
+}
diff --git a/src/TwitchLib.Communication.Tests/Models/ReconnectionPolicyTests.cs b/src/TwitchLib.Communication.Tests/Models/ReconnectionPolicyTests.cs
index e67a286..24d1186 100644
--- a/src/TwitchLib.Communication.Tests/Models/ReconnectionPolicyTests.cs
+++ b/src/TwitchLib.Communication.Tests/Models/ReconnectionPolicyTests.cs
@@ -2,39 +2,38 @@
using TwitchLib.Communication.Models;
using Xunit;
-namespace TwitchLib.Communication.Tests.Models
+namespace TwitchLib.Communication.Tests.Models;
+
+public class ReconnectionPolicyTests
{
- public class ReconnectionPolicyTests
+ ///
+ /// Checks
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Fact]
+ public void ReconnectionPolicy_OmitReconnect()
{
- ///
- /// Checks
- ///
- ///
- ///
- ///
- ///
- [Fact]
- public void ReconnectionPolicy_OmitReconnect()
+ try
+ {
+ ReconnectionPolicy reconnectionPolicy = new NoReconnectionPolicy();
+ Assert.False(reconnectionPolicy.AreAttemptsComplete());
+ reconnectionPolicy.ProcessValues();
+ Assert.True(reconnectionPolicy.AreAttemptsComplete());
+ // in case of a normal connect, we expect the ReconnectionPolicy to be reset
+ reconnectionPolicy.Reset(false);
+ Assert.False(reconnectionPolicy.AreAttemptsComplete());
+ reconnectionPolicy.ProcessValues();
+ Assert.True(reconnectionPolicy.AreAttemptsComplete());
+ // in case of a reconnect, we expect the ReconnectionPolicy not to be reset
+ reconnectionPolicy.Reset(true);
+ Assert.True(reconnectionPolicy.AreAttemptsComplete());
+ }
+ catch (Exception e)
{
- try
- {
- ReconnectionPolicy reconnectionPolicy = new NoReconnectionPolicy();
- Assert.False(reconnectionPolicy.AreAttemptsComplete());
- reconnectionPolicy.ProcessValues();
- Assert.True(reconnectionPolicy.AreAttemptsComplete());
- // in case of a normal connect, we expect the ReconnectionPolicy to be reset
- reconnectionPolicy.Reset(false);
- Assert.False(reconnectionPolicy.AreAttemptsComplete());
- reconnectionPolicy.ProcessValues();
- Assert.True(reconnectionPolicy.AreAttemptsComplete());
- // in case of a reconnect, we expect the ReconnectionPolicy not to be reset
- reconnectionPolicy.Reset(true);
- Assert.True(reconnectionPolicy.AreAttemptsComplete());
- }
- catch (Exception e)
- {
- Assert.Fail(e.ToString());
- }
+ Assert.Fail(e.ToString());
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TwitchLib.Communication.Tests/TwitchLib.Communication.Tests.csproj b/src/TwitchLib.Communication.Tests/TwitchLib.Communication.Tests.csproj
index b777674..c245c36 100644
--- a/src/TwitchLib.Communication.Tests/TwitchLib.Communication.Tests.csproj
+++ b/src/TwitchLib.Communication.Tests/TwitchLib.Communication.Tests.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net7.0
false
enable
disable
diff --git a/src/TwitchLib.Communication/Clients/ClientBase.cs b/src/TwitchLib.Communication/Clients/ClientBase.cs
index a9d9b0f..0dfc91f 100644
--- a/src/TwitchLib.Communication/Clients/ClientBase.cs
+++ b/src/TwitchLib.Communication/Clients/ClientBase.cs
@@ -1,369 +1,384 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using TwitchLib.Communication.Events;
using TwitchLib.Communication.Extensions;
using TwitchLib.Communication.Interfaces;
using TwitchLib.Communication.Models;
using TwitchLib.Communication.Services;
-namespace TwitchLib.Communication.Clients
+namespace TwitchLib.Communication.Clients;
+
+///
+/// This bundles almost everything that and have in common
+/// to be able to
+///
+/// -
+/// pass instances of this to
+///
+/// -
+/// and to access Methods of this instance within
+///
+///
+///
+public abstract class ClientBase : IClient
+ where T : IDisposable
{
+ private readonly SemaphoreSlim _semaphore = new(1, 1);
+ private readonly NetworkServices _networkServices;
+ private CancellationTokenSource _cancellationTokenSource;
+
///
- /// This bundles almost everything that and have in common
- /// to be able to
- ///
- /// -
- /// pass instances of this to and
- ///
- /// -
- /// and to access Methods of this instance within and
- ///
- ///
+ /// This is used for
+ /// whenever a call to is made
+ ///
+ internal CancellationToken Token => _cancellationTokenSource.Token;
+
+ internal static TimeSpan TimeOutEstablishConnection => TimeSpan.FromSeconds(15);
+
+ protected readonly ILogger>? Logger;
+
+ protected abstract string Url { get; }
+
+ ///
+ /// The underlying client.
+ ///
+ public T? Client { get; private set; }
+
+ ///
+ public abstract bool IsConnected { get; }
+
+ ///
+ public IClientOptions Options { get; }
+
+ ///
+ public event AsyncEventHandler? OnConnected;
+
+ ///
+ public event AsyncEventHandler? OnDisconnected;
+
+ ///
+ public event AsyncEventHandler? OnError;
+
+ ///
+ public event AsyncEventHandler? OnFatality;
+
+ ///
+ public event AsyncEventHandler? OnMessage;
+
+ ///
+ public event AsyncEventHandler? OnSendFailed;
+
+ ///
+ public event AsyncEventHandler? OnReconnected;
+
+ internal ClientBase(
+ IClientOptions? options,
+ ILogger>? logger)
+ {
+ Logger = logger;
+ _cancellationTokenSource = new CancellationTokenSource();
+ Options = options ?? new ClientOptions();
+ _networkServices = new NetworkServices(this, logger);
+ }
+
+ ///
+ /// Wont raise the given if .IsCancellationRequested
///
- public abstract class ClientBase : IClient
- where T : IDisposable
+ private async Task RaiseSendFailed(OnSendFailedEventArgs eventArgs)
{
- private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
- private readonly NetworkServices _networkServices;
- private CancellationTokenSource _cancellationTokenSource;
-
- ///
- /// This is used for
- /// whenever a call to is made
- ///
- internal CancellationToken Token => _cancellationTokenSource.Token;
-
- internal static TimeSpan TimeOutEstablishConnection => TimeSpan.FromSeconds(15);
-
- protected readonly ILogger>? Logger;
-
- protected abstract string Url { get; }
-
- ///
- /// The underlying client.
- ///
- public T? Client { get; private set; }
-
- public abstract bool IsConnected { get; }
-
- public IClientOptions Options { get; }
-
- public event AsyncEventHandler? OnConnected;
- public event AsyncEventHandler? OnDisconnected;
- public event AsyncEventHandler? OnError;
- public event AsyncEventHandler? OnFatality;
- public event AsyncEventHandler? OnMessage;
- public event AsyncEventHandler? OnSendFailed;
- public event AsyncEventHandler? OnReconnected;
-
- internal ClientBase(
- IClientOptions? options,
- ILogger>? logger)
+ Logger?.TraceMethodCall(GetType());
+ if (Token.IsCancellationRequested)
{
- Logger = logger;
- _cancellationTokenSource = new CancellationTokenSource();
- Options = options ?? new ClientOptions();
- _networkServices = new NetworkServices(this, logger);
+ return;
}
- ///
- /// Wont raise the given if .IsCancellationRequested
- ///
- private async Task RaiseSendFailed(OnSendFailedEventArgs eventArgs)
- {
- Logger?.TraceMethodCall(GetType());
- if (Token.IsCancellationRequested)
- {
- return;
- }
+ if (OnSendFailed != null) await OnSendFailed.Invoke(this, eventArgs);
+ }
- if(OnSendFailed != null) await OnSendFailed.Invoke(this, eventArgs);
+ ///
+ /// Wont raise the given if .IsCancellationRequested
+ ///
+ internal async Task RaiseError(OnErrorEventArgs eventArgs)
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (Token.IsCancellationRequested)
+ {
+ return;
}
- ///
- /// Wont raise the given if .IsCancellationRequested
- ///
- internal async Task RaiseError(OnErrorEventArgs eventArgs)
- {
- Logger?.TraceMethodCall(GetType());
- if (Token.IsCancellationRequested)
- {
- return;
- }
+ if (OnError != null) await OnError.Invoke(this, eventArgs);
+ }
- if (OnError != null) await OnError.Invoke(this, eventArgs);
+ ///
+ /// Wont raise the given if .IsCancellationRequested
+ ///
+ private async Task RaiseReconnected()
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (Token.IsCancellationRequested)
+ {
+ return;
}
- ///
- /// Wont raise the given if .IsCancellationRequested
- ///
- private async Task RaiseReconnected()
- {
- Logger?.TraceMethodCall(GetType());
- if (Token.IsCancellationRequested)
- {
- return;
- }
+ if (OnReconnected != null) await OnReconnected.Invoke(this, new OnConnectedEventArgs());
+ }
- if (OnReconnected != null) await OnReconnected.Invoke(this, new OnConnectedEventArgs());
+ ///
+ /// Wont raise the given if .IsCancellationRequested
+ ///
+ internal async Task RaiseMessage(OnMessageEventArgs eventArgs)
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (Token.IsCancellationRequested)
+ {
+ return;
}
- ///
- /// Wont raise the given if .IsCancellationRequested
- ///
- internal async Task RaiseMessage(OnMessageEventArgs eventArgs)
- {
- Logger?.TraceMethodCall(GetType());
- if (Token.IsCancellationRequested)
- {
- return;
- }
+ if (OnMessage != null) await OnMessage.Invoke(this, eventArgs);
+ }
- if (OnMessage != null) await OnMessage.Invoke(this, eventArgs);
+ ///
+ /// Wont raise the given if .IsCancellationRequested
+ ///
+ internal async Task RaiseFatal(Exception? ex = null)
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (Token.IsCancellationRequested)
+ {
+ return;
}
- ///
- /// Wont raise the given if .IsCancellationRequested
- ///
- internal async Task RaiseFatal(Exception? ex = null)
- {
- Logger?.TraceMethodCall(GetType());
- if (Token.IsCancellationRequested)
- {
- return;
- }
+ var onFatalErrorEventArgs = ex != null
+ ? new OnFatalErrorEventArgs(ex)
+ : new OnFatalErrorEventArgs("Fatal network error.");
- var onFatalErrorEventArgs = ex != null
- ? new OnFatalErrorEventArgs(ex)
- : new OnFatalErrorEventArgs("Fatal network error.");
+ if (OnFatality != null) await OnFatality.Invoke(this, onFatalErrorEventArgs);
+ }
- if (OnFatality != null) await OnFatality.Invoke(this, onFatalErrorEventArgs);
- }
+ private async Task RaiseDisconnected()
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (OnDisconnected != null) await OnDisconnected.Invoke(this, new OnDisconnectedEventArgs());
+ }
- private async Task RaiseDisconnected()
- {
- Logger?.TraceMethodCall(GetType());
- if (OnDisconnected != null) await OnDisconnected.Invoke(this, new OnDisconnectedEventArgs());
- }
+ private async Task RaiseConnected()
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (OnConnected != null) await OnConnected.Invoke(this, new OnConnectedEventArgs());
+ }
+
+ ///
+ public async Task SendAsync(string message)
+ {
+ Logger?.TraceMethodCall(GetType());
- private async Task RaiseConnected()
+ await _semaphore.WaitAsync(Token);
+ try
{
- Logger?.TraceMethodCall(GetType());
- if (OnConnected != null) await OnConnected.Invoke(this, new OnConnectedEventArgs());
+ await ClientSendAsync(message);
+ return true;
}
-
- public async Task SendAsync(string message)
+ catch (Exception e)
{
- Logger?.TraceMethodCall(GetType());
-
- await _semaphore.WaitAsync(Token);
- try
- {
- await ClientSendAsync(message);
- return true;
- }
- catch (Exception e)
- {
- await RaiseSendFailed(new OnSendFailedEventArgs(e, message));
- return false;
- }
- finally
- {
- _semaphore.Release();
- }
+ await RaiseSendFailed(new OnSendFailedEventArgs(e, message));
+ return false;
}
-
- public Task OpenAsync()
+ finally
{
- Logger?.TraceMethodCall(GetType());
- return OpenPrivateAsync(false);
+ _semaphore.Release();
}
+ }
- public async Task CloseAsync()
- {
- Logger?.TraceMethodCall(GetType());
+ ///
+ public Task OpenAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
+ return OpenPrivateAsync(false);
+ }
- // ClosePrivate() also handles IClientOptions.DisconnectWait
- await ClosePrivateAsync();
- }
+ ///
+ public async Task CloseAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
- ///
- ///
- ///
- public void Dispose()
- {
- Logger?.TraceMethodCall(GetType());
- CloseAsync().GetAwaiter().GetResult();
- GC.SuppressFinalize(this);
- }
+ // ClosePrivate() also handles IClientOptions.DisconnectWait
+ await ClosePrivateAsync();
+ }
- public async Task ReconnectAsync()
- {
- Logger?.TraceMethodCall(GetType());
+ ///
+ ///
+ ///
+ public void Dispose()
+ {
+ Logger?.TraceMethodCall(GetType());
+ CloseAsync().GetAwaiter().GetResult();
+ GC.SuppressFinalize(this);
+ }
- return await ReconnectInternalAsync();
- }
+ ///
+ public async Task ReconnectAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
- private async Task OpenPrivateAsync(bool isReconnect)
+ return await ReconnectInternalAsync();
+ }
+
+ private async Task OpenPrivateAsync(bool isReconnect)
+ {
+ Logger?.TraceMethodCall(GetType());
+ try
{
- Logger?.TraceMethodCall(GetType());
- try
+ if (Token.IsCancellationRequested)
{
- if (Token.IsCancellationRequested)
- {
- return false;
- }
+ return false;
+ }
- if (IsConnected)
- {
- return true;
- }
+ if (IsConnected)
+ {
+ return true;
+ }
- // Always create new client when opening new connection
- Client = CreateClient();
-
- var first = true;
- Options.ReconnectionPolicy.Reset(isReconnect);
-
- while (!IsConnected &&
- !Options.ReconnectionPolicy.AreAttemptsComplete())
- {
- Logger?.TraceAction(GetType(), "try to connect");
- if (!first)
- {
- await Task.Delay(Options.ReconnectionPolicy.GetReconnectInterval(), CancellationToken.None);
- }
-
- await ConnectClientAsync();
- Options.ReconnectionPolicy.ProcessValues();
- first = false;
- }
+ // Always create new client when opening new connection
+ Client = CreateClient();
- if (!IsConnected)
- {
- Logger?.TraceAction(GetType(), "Client couldn't establish a connection");
- await RaiseFatal();
- return false;
- }
+ var first = true;
+ Options.ReconnectionPolicy.Reset(isReconnect);
- Logger?.TraceAction(GetType(), "Client established a connection");
- _networkServices.Start();
-
- if (!isReconnect)
+ while (!IsConnected &&
+ !Options.ReconnectionPolicy.AreAttemptsComplete())
+ {
+ Logger?.TraceAction(GetType(), "try to connect");
+ if (!first)
{
- await RaiseConnected();
+ await Task.Delay(Options.ReconnectionPolicy.GetReconnectInterval(), CancellationToken.None);
}
- return true;
+ await ConnectClientAsync();
+ Options.ReconnectionPolicy.ProcessValues();
+ first = false;
}
- catch (Exception ex)
+
+ if (!IsConnected)
{
- Logger?.LogExceptionAsError(GetType(), ex);
- await RaiseError(new OnErrorEventArgs(ex));
+ Logger?.TraceAction(GetType(), "Client couldn't establish a connection");
await RaiseFatal();
return false;
}
- }
- ///
- /// Stops
- /// by calling
- ///
- /// and enforces the
- ///
- /// afterwards it waits for the via given amount of milliseconds
- ///
- ///
- /// will keep running,
- /// because itself issued this call by calling
- ///
- private async Task ClosePrivateAsync()
- {
- Logger?.TraceMethodCall(GetType());
-
- await _networkServices.StopAsync();
-
- // This cancellation traverse up to NetworkServices.ListenTask
- _cancellationTokenSource.Cancel();
- Logger?.TraceAction(GetType(),
- $"{nameof(_cancellationTokenSource)}.{nameof(_cancellationTokenSource.Cancel)} is called");
-
- CloseClient();
- await RaiseDisconnected();
- _cancellationTokenSource = new CancellationTokenSource();
-
- await Task.Delay(TimeSpan.FromMilliseconds(Options.DisconnectWait), CancellationToken.None);
- }
+ Logger?.TraceAction(GetType(), "Client established a connection");
+ _networkServices.Start();
- ///
- /// Send method for the client.
- ///
- ///
- /// Message to be send
- ///
- protected abstract Task ClientSendAsync(string message);
-
- ///
- /// Instantiate the underlying client.
- ///
- protected abstract T CreateClient();
-
- ///
- /// one of the following specific methods
- ///
- /// -
- ///
- ///
- /// -
- ///
- ///
- ///
- /// calls to one of the methods mentioned above,
- /// also Dispose() the respective client,
- /// so no additional Dispose() is needed
- ///
- protected abstract void CloseClient();
-
- ///
- /// Connect the client.
- ///
- protected abstract Task ConnectClientAsync();
-
- ///
- /// To issue a reconnect
- ///
- /// especially for the
- ///
- /// it stops all but !
- ///
- ///
- /// see also :
- ///
- ///
- ///
- ///
- /// if a connection could be established, otherwise
- ///
- internal async Task ReconnectInternalAsync()
- {
- Logger?.TraceMethodCall(GetType());
-
- await ClosePrivateAsync();
- var reconnected = await OpenPrivateAsync(true);
- if (reconnected)
+ if (!isReconnect)
{
- await RaiseReconnected();
+ await RaiseConnected();
}
- return reconnected;
+ return true;
}
+ catch (Exception ex)
+ {
+ Logger?.LogExceptionAsError(GetType(), ex);
+ await RaiseError(new OnErrorEventArgs(ex));
+ await RaiseFatal();
+ return false;
+ }
+ }
+
+ ///
+ /// Stops
+ /// by calling
+ ///
+ /// and enforces the
+ ///
+ /// afterwards it waits for the via given amount of milliseconds
+ ///
+ ///
+ /// will keep running,
+ /// because itself issued this call by calling
+ ///
+ private async Task ClosePrivateAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
+
+ await _networkServices.StopAsync();
+
+ // This cancellation traverse up to NetworkServices.ListenTask
+ _cancellationTokenSource.Cancel();
+ Logger?.TraceAction(GetType(),
+ $"{nameof(_cancellationTokenSource)}.{nameof(_cancellationTokenSource.Cancel)} is called");
- ///
- /// just the Action that listens for new Messages
- /// the corresponding is held by
- ///
- internal abstract Task ListenTaskActionAsync();
+ CloseClient();
+ await RaiseDisconnected();
+ _cancellationTokenSource = new CancellationTokenSource();
+
+ await Task.Delay(TimeSpan.FromMilliseconds(Options.DisconnectWait), CancellationToken.None);
}
-}
\ No newline at end of file
+
+ ///
+ /// Send method for the client.
+ ///
+ ///
+ /// Message to be send
+ ///
+ protected abstract Task ClientSendAsync(string message);
+
+ ///
+ /// Instantiate the underlying client.
+ ///
+ protected abstract T CreateClient();
+
+ ///
+ /// one of the following specific methods
+ ///
+ /// -
+ ///
+ ///
+ /// -
+ ///
+ ///
+ ///
+ /// calls to one of the methods mentioned above,
+ /// also Dispose() the respective client,
+ /// so no additional Dispose() is needed
+ ///
+ protected abstract void CloseClient();
+
+ ///
+ /// Connect the client.
+ ///
+ protected abstract Task ConnectClientAsync();
+
+ ///
+ /// To issue a reconnect
+ ///
+ /// especially for the
+ ///
+ /// it stops all but !
+ ///
+ ///
+ /// see also :
+ ///
+ ///
+ ///
+ ///
+ /// if a connection could be established, otherwise
+ ///
+ internal async Task ReconnectInternalAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
+
+ await ClosePrivateAsync();
+ var reconnected = await OpenPrivateAsync(true);
+ if (reconnected)
+ {
+ await RaiseReconnected();
+ }
+
+ return reconnected;
+ }
+
+ ///
+ /// just the Action that listens for new Messages
+ /// the corresponding is held by
+ ///
+ internal abstract Task ListenTaskActionAsync();
+}
diff --git a/src/TwitchLib.Communication/Clients/TcpClient.cs b/src/TwitchLib.Communication/Clients/TcpClient.cs
index 6340ef1..1bfc475 100644
--- a/src/TwitchLib.Communication/Clients/TcpClient.cs
+++ b/src/TwitchLib.Communication/Clients/TcpClient.cs
@@ -1,104 +1,103 @@
-using System;
-using System.IO;
-using System.Net.Security;
-using System.Threading;
-using System.Threading.Tasks;
+using System.Net.Security;
using Microsoft.Extensions.Logging;
using TwitchLib.Communication.Events;
using TwitchLib.Communication.Extensions;
using TwitchLib.Communication.Interfaces;
-namespace TwitchLib.Communication.Clients
+namespace TwitchLib.Communication.Clients;
+
+public class TcpClient : ClientBase
{
- public class TcpClient : ClientBase
- {
- private StreamReader? _reader;
- private StreamWriter? _writer;
+ private StreamReader? _reader;
+ private StreamWriter? _writer;
- protected override string Url => "irc.chat.twitch.tv";
+ ///
+ protected override string Url => "irc.chat.twitch.tv";
- private int Port => Options.UseSsl ? 6697 : 6667;
+ private int Port => Options.UseSsl ? 6697 : 6667;
- public override bool IsConnected => Client?.Connected ?? false;
+ ///
+ public override bool IsConnected => Client?.Connected ?? false;
+
+ public TcpClient(
+ IClientOptions? options = null,
+ ILogger? logger = null)
+ : base(options, logger)
+ {
+ }
- public TcpClient(
- IClientOptions? options = null,
- ILogger? logger = null)
- : base(options, logger)
+ internal override async Task ListenTaskActionAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (_reader == null)
{
+ var ex = new InvalidOperationException($"{nameof(_reader)} was null!");
+ Logger?.LogExceptionAsError(GetType(), ex);
+ await RaiseFatal(ex);
+ throw ex;
}
- internal override async Task ListenTaskActionAsync()
+ while (IsConnected)
{
- Logger?.TraceMethodCall(GetType());
- if (_reader == null)
- {
- var ex = new InvalidOperationException($"{nameof(_reader)} was null!");
- Logger?.LogExceptionAsError(GetType(), ex);
- await RaiseFatal(ex);
- throw ex;
- }
-
- while (IsConnected)
+ try
{
- try
- {
- var input = await _reader.ReadLineAsync();
- if (input is null)
- {
- continue;
- }
-
- await RaiseMessage(new OnMessageEventArgs(input));
- }
- catch (Exception ex) when (ex.GetType() == typeof(TaskCanceledException) ||
- ex.GetType() == typeof(OperationCanceledException))
- {
- // occurs if the Tasks are canceled by the CancellationTokenSource.Token
- Logger?.LogExceptionAsInformation(GetType(), ex);
- }
- catch (Exception ex)
+ var input = await _reader.ReadLineAsync();
+ if (input is null)
{
- Logger?.LogExceptionAsError(GetType(), ex);
- await RaiseError(new OnErrorEventArgs(ex));
- break;
+ continue;
}
- }
- }
-
- protected override async Task ClientSendAsync(string message)
- {
- Logger?.TraceMethodCall(GetType());
- // this is not thread safe
- // this method should only be called from 'ClientBase.Send()'
- // where its call gets synchronized/locked
- // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream?view=netstandard-2.0#remarks
- if (_writer == null)
+ await RaiseMessage(new OnMessageEventArgs(input));
+ }
+ catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
+ {
+ // occurs if the Tasks are canceled by the CancellationTokenSource.Token
+ Logger?.LogExceptionAsInformation(GetType(), ex);
+ }
+ catch (Exception ex)
{
- var ex = new InvalidOperationException($"{nameof(_writer)} was null!");
Logger?.LogExceptionAsError(GetType(), ex);
- await RaiseFatal(ex);
- throw ex;
+ await RaiseError(new OnErrorEventArgs(ex));
+ break;
}
+ }
+ }
- await _writer.WriteLineAsync(message);
- await _writer.FlushAsync();
+ ///
+ protected override async Task ClientSendAsync(string message)
+ {
+ Logger?.TraceMethodCall(GetType());
+
+ // this is not thread safe
+ // this method should only be called from 'ClientBase.Send()'
+ // where its call gets synchronized/locked
+ // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream?view=netstandard-2.0#remarks
+ if (_writer == null)
+ {
+ var ex = new InvalidOperationException($"{nameof(_writer)} was null!");
+ Logger?.LogExceptionAsError(GetType(), ex);
+ await RaiseFatal(ex);
+ throw ex;
}
- protected override async Task ConnectClientAsync()
+ await _writer.WriteLineAsync(message);
+ await _writer.FlushAsync();
+ }
+
+ ///
+ protected override async Task ConnectClientAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (Client == null)
{
- Logger?.TraceMethodCall(GetType());
- if (Client == null)
- {
- Exception ex = new InvalidOperationException($"{nameof(Client)} was null!");
- Logger?.LogExceptionAsError(GetType(), ex);
- throw ex;
- }
+ Exception ex = new InvalidOperationException($"{nameof(Client)} was null!");
+ Logger?.LogExceptionAsError(GetType(), ex);
+ throw ex;
+ }
- try
- {
- // https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios
+ try
+ {
+ // https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios
#if NET6_0_OR_GREATER
// within the following thread:
// https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout
@@ -126,52 +125,52 @@ protected override async Task ConnectClientAsync()
delayTaskCancellationTokenSource.Cancel();
}
#endif
- if (!Client.Connected)
- {
- Logger?.TraceAction(GetType(), "Client couldn't establish connection");
- return;
- }
-
- Logger?.TraceAction(GetType(), "Client established connection successfully");
- Stream stream = Client.GetStream();
- if (Options.UseSsl)
- {
- var ssl = new SslStream(stream, false);
- await ssl.AuthenticateAsClientAsync(Url);
- stream = ssl;
- }
- _reader = new StreamReader(stream);
- _writer = new StreamWriter(stream);
- }
- catch (Exception ex) when (ex.GetType() == typeof(TaskCanceledException) ||
- ex.GetType() == typeof(OperationCanceledException))
+ if (!Client.Connected)
{
- // occurs if the Tasks are canceled by the CancellationTokenSource.Token
- Logger?.LogExceptionAsInformation(GetType(), ex);
+ Logger?.TraceAction(GetType(), "Client couldn't establish connection");
+ return;
}
- catch (Exception ex)
+
+ Logger?.TraceAction(GetType(), "Client established connection successfully");
+ Stream stream = Client.GetStream();
+ if (Options.UseSsl)
{
- Logger?.LogExceptionAsError(GetType(), ex);
+ var ssl = new SslStream(stream, false);
+ await ssl.AuthenticateAsClientAsync(Url);
+ stream = ssl;
}
+ _reader = new StreamReader(stream);
+ _writer = new StreamWriter(stream);
}
-
- protected override System.Net.Sockets.TcpClient CreateClient()
+ catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
{
- Logger?.TraceMethodCall(GetType());
-
- return new System.Net.Sockets.TcpClient
- {
- // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcpclient.lingerstate?view=netstandard-2.0#remarks
- LingerState = new System.Net.Sockets.LingerOption(true, 0)
- };
+ // occurs if the Tasks are canceled by the CancellationTokenSource.Token
+ Logger?.LogExceptionAsInformation(GetType(), ex);
}
-
- protected override void CloseClient()
+ catch (Exception ex)
{
- Logger?.TraceMethodCall(GetType());
- _reader?.Dispose();
- _writer?.Dispose();
- Client?.Dispose();
+ Logger?.LogExceptionAsError(GetType(), ex);
}
}
+
+ ///
+ protected override System.Net.Sockets.TcpClient CreateClient()
+ {
+ Logger?.TraceMethodCall(GetType());
+
+ return new System.Net.Sockets.TcpClient
+ {
+ // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcpclient.lingerstate?view=netstandard-2.0#remarks
+ LingerState = new System.Net.Sockets.LingerOption(true, 0)
+ };
+ }
+
+ ///
+ protected override void CloseClient()
+ {
+ Logger?.TraceMethodCall(GetType());
+ _reader?.Dispose();
+ _writer?.Dispose();
+ Client?.Dispose();
+ }
}
\ No newline at end of file
diff --git a/src/TwitchLib.Communication/Clients/WebsocketClient.cs b/src/TwitchLib.Communication/Clients/WebsocketClient.cs
index 6902736..516175e 100644
--- a/src/TwitchLib.Communication/Clients/WebsocketClient.cs
+++ b/src/TwitchLib.Communication/Clients/WebsocketClient.cs
@@ -1,155 +1,158 @@
-using System;
-using System.IO;
-using System.Net.WebSockets;
+using System.Net.WebSockets;
using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using TwitchLib.Communication.Enums;
using TwitchLib.Communication.Events;
using TwitchLib.Communication.Extensions;
using TwitchLib.Communication.Interfaces;
-namespace TwitchLib.Communication.Clients
+namespace TwitchLib.Communication.Clients;
+
+public class WebSocketClient : ClientBase
{
- public class WebSocketClient : ClientBase
- {
- protected override string Url { get; }
+ protected override string Url { get; }
- public override bool IsConnected => Client?.State == WebSocketState.Open;
+ public override bool IsConnected => Client?.State == WebSocketState.Open;
- public WebSocketClient(
- IClientOptions? options = null,
- ILogger? logger = null)
- : base(options, logger)
+ public WebSocketClient(
+ IClientOptions? options = null,
+ ILogger? logger = null)
+ : base(options, logger)
+ {
+ switch (Options.ClientType)
{
- switch (Options.ClientType)
- {
- case ClientType.Chat:
- Url = Options.UseSsl ? "wss://irc-ws.chat.twitch.tv:443" : "ws://irc-ws.chat.twitch.tv:80";
- break;
- case ClientType.PubSub:
- Url = Options.UseSsl ? "wss://pubsub-edge.twitch.tv:443" : "ws://pubsub-edge.twitch.tv:80";
- break;
- default:
- var ex = new ArgumentOutOfRangeException(nameof(Options.ClientType));
- Logger?.LogExceptionAsError(GetType(), ex);
- throw ex;
- }
+ case ClientType.Chat:
+ Url = Options.UseSsl ? "wss://irc-ws.chat.twitch.tv:443" : "ws://irc-ws.chat.twitch.tv:80";
+ break;
+ case ClientType.PubSub:
+ Url = Options.UseSsl ? "wss://pubsub-edge.twitch.tv:443" : "ws://pubsub-edge.twitch.tv:80";
+ break;
+ default:
+ var ex = new ArgumentOutOfRangeException(nameof(Options.ClientType));
+ Logger?.LogExceptionAsError(GetType(), ex);
+ throw ex;
+ }
+ }
+
+ internal override async Task ListenTaskActionAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (Client == null)
+ {
+ var ex = new InvalidOperationException($"{nameof(Client)} was null!");
+ Logger?.LogExceptionAsError(GetType(), ex);
+ await RaiseFatal(ex);
+ throw ex;
}
- internal override async Task ListenTaskActionAsync()
+ var memoryStream = new MemoryStream();
+ var bytes = new byte[1024];
+#if NET
+ var buffer = new Memory(bytes);
+ ValueWebSocketReceiveResult result;
+#else
+ var buffer = new ArraySegment(bytes);
+ WebSocketReceiveResult result;
+#endif
+ while (IsConnected)
{
- Logger?.TraceMethodCall(GetType());
- if (Client == null)
+ try
+ {
+ result = await Client.ReceiveAsync(buffer, Token);
+ }
+ catch (TaskCanceledException)
+ {
+ // Swallow any cancellation exceptions
+ break;
+ }
+ catch (OperationCanceledException ex)
+ {
+ Logger?.LogExceptionAsInformation(GetType(), ex);
+ break;
+ }
+ catch (Exception ex)
{
- var ex = new InvalidOperationException($"{nameof(Client)} was null!");
Logger?.LogExceptionAsError(GetType(), ex);
- await RaiseFatal(ex);
- throw ex;
+ await RaiseError(new OnErrorEventArgs(ex));
+ break;
}
- var memoryStream = new MemoryStream();
- var bytes = new byte[1024];
- var buffer = new ArraySegment(bytes);
- WebSocketReceiveResult result;
- while (IsConnected)
+ switch (result.MessageType)
{
- try
- {
- result = await Client.ReceiveAsync(buffer, Token);
- }
- catch (TaskCanceledException _)
- {
- // Swallow any cancellation exceptions
+ case WebSocketMessageType.Close:
+ await CloseAsync();
break;
- }
- catch (OperationCanceledException ex)
- {
- Logger?.LogExceptionAsInformation(GetType(), ex);
+ case WebSocketMessageType.Text:
+ if (result.EndOfMessage && memoryStream.Position == 0)
+ {
+ //optimization when we can read the whole message at once
+ var message = Encoding.UTF8.GetString(bytes, 0, result.Count);
+ await RaiseMessage(new OnMessageEventArgs(message));
+ break;
+ }
+ memoryStream.Write(bytes, 0, result.Count);
+ if (result.EndOfMessage)
+ {
+ var message = Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Position);
+ await RaiseMessage(new OnMessageEventArgs(message));
+ memoryStream.Position = 0;
+ }
break;
- }
- catch (Exception ex)
- {
- Logger?.LogExceptionAsError(GetType(), ex);
- await RaiseError(new OnErrorEventArgs(ex));
+ case WebSocketMessageType.Binary:
+ //todo
break;
- }
-
- switch (result.MessageType)
- {
- case WebSocketMessageType.Close:
- await CloseAsync();
- break;
- case WebSocketMessageType.Text:
- if (result.EndOfMessage && memoryStream.Position == 0)
- {
- //optimization when we can read the whole message at once
- var message = Encoding.UTF8.GetString(bytes, 0, result.Count);
- await RaiseMessage(new OnMessageEventArgs(message));
- break;
- }
- memoryStream.Write(bytes, 0, result.Count);
- if (result.EndOfMessage)
- {
- var message = Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Position);
- await RaiseMessage(new OnMessageEventArgs(message));
- memoryStream.Position = 0;
- }
- break;
- case WebSocketMessageType.Binary:
- //todo
- break;
- default:
- Exception ex = new ArgumentOutOfRangeException();
- Logger?.LogExceptionAsError(GetType(), ex);
- throw ex;
- }
+ default:
+ Exception ex = new ArgumentOutOfRangeException();
+ Logger?.LogExceptionAsError(GetType(), ex);
+ throw ex;
}
}
+ }
- protected override async Task ClientSendAsync(string message)
- {
- Logger?.TraceMethodCall(GetType());
-
- // this is not thread safe
- // this method should only be called from 'ClientBase.Send()'
- // where its call gets synchronized/locked
- // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream?view=netstandard-2.0#remarks
-
- // https://stackoverflow.com/a/59619916
- // links from within this thread:
- // the 4th point: https://www.codetinkerer.com/2018/06/05/aspnet-core-websockets.html
- // https://github.com/dotnet/corefx/blob/d6b11250b5113664dd3701c25bdf9addfacae9cc/src/Common/src/System/Net/WebSockets/ManagedWebSocket.cs#L22-L28
- if (Client == null)
- {
- var ex = new InvalidOperationException($"{nameof(Client)} was null!");
- Logger?.LogExceptionAsError(GetType(), ex);
- await RaiseFatal(ex);
- throw ex;
- }
+ ///
+ protected override async Task ClientSendAsync(string message)
+ {
+ Logger?.TraceMethodCall(GetType());
+
+ // this is not thread safe
+ // this method should only be called from 'ClientBase.Send()'
+ // where its call gets synchronized/locked
+ // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream?view=netstandard-2.0#remarks
- var bytes = Encoding.UTF8.GetBytes(message);
- await Client.SendAsync(new ArraySegment(bytes),
- WebSocketMessageType.Text,
- true,
- Token);
+ // https://stackoverflow.com/a/59619916
+ // links from within this thread:
+ // the 4th point: https://www.codetinkerer.com/2018/06/05/aspnet-core-websockets.html
+ // https://github.com/dotnet/corefx/blob/d6b11250b5113664dd3701c25bdf9addfacae9cc/src/Common/src/System/Net/WebSockets/ManagedWebSocket.cs#L22-L28
+ if (Client == null)
+ {
+ var ex = new InvalidOperationException($"{nameof(Client)} was null!");
+ Logger?.LogExceptionAsError(GetType(), ex);
+ await RaiseFatal(ex);
+ throw ex;
}
- protected override async Task ConnectClientAsync()
+ var bytes = Encoding.UTF8.GetBytes(message);
+ await Client.SendAsync(new ArraySegment(bytes),
+ WebSocketMessageType.Text,
+ true,
+ Token);
+ }
+
+ ///
+ protected override async Task ConnectClientAsync()
+ {
+ Logger?.TraceMethodCall(GetType());
+ if (Client == null)
{
- Logger?.TraceMethodCall(GetType());
- if (Client == null)
- {
- var ex = new InvalidOperationException($"{nameof(Client)} was null!");
- Logger?.LogExceptionAsError(GetType(), ex);
- await RaiseFatal(ex);
- throw ex;
- }
+ var ex = new InvalidOperationException($"{nameof(Client)} was null!");
+ Logger?.LogExceptionAsError(GetType(), ex);
+ await RaiseFatal(ex);
+ throw ex;
+ }
- try
- {
- // https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios
+ try
+ {
+ // https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios
#if NET6_0_OR_GREATER
// within the following thread:
// https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout
@@ -176,34 +179,34 @@ protected override async Task ConnectClientAsync()
delayTaskCancellationTokenSource.Cancel();
}
#endif
- if (!IsConnected)
- {
- Logger?.TraceAction(GetType(), "Client couldn't establish connection");
- }
- }
- catch (Exception ex) when (ex.GetType() == typeof(TaskCanceledException) ||
- ex.GetType() == typeof(OperationCanceledException))
+ if (!IsConnected)
{
- // occurs if the Tasks are canceled by the CancellationTokenSource.Token
- Logger?.LogExceptionAsInformation(GetType(), ex);
- }
- catch (Exception ex)
- {
- Logger?.LogExceptionAsError(GetType(), ex);
+ Logger?.TraceAction(GetType(), "Client couldn't establish connection");
}
}
-
- protected override ClientWebSocket CreateClient()
+ catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
{
- Logger?.TraceMethodCall(GetType());
- return new ClientWebSocket();
+ // occurs if the Tasks are canceled by the CancellationTokenSource.Token
+ Logger?.LogExceptionAsInformation(GetType(), ex);
}
-
- protected override void CloseClient()
+ catch (Exception ex)
{
- Logger?.TraceMethodCall(GetType());
- Client?.Abort();
- Client?.Dispose();
+ Logger?.LogExceptionAsError(GetType(), ex);
}
}
+
+ ///
+ protected override ClientWebSocket CreateClient()
+ {
+ Logger?.TraceMethodCall(GetType());
+ return new ClientWebSocket();
+ }
+
+ ///
+ protected override void CloseClient()
+ {
+ Logger?.TraceMethodCall(GetType());
+ Client?.Abort();
+ Client?.Dispose();
+ }
}
\ No newline at end of file
diff --git a/src/TwitchLib.Communication/Enums/ClientType.cs b/src/TwitchLib.Communication/Enums/ClientType.cs
index dee4b24..7e8bea7 100644
--- a/src/TwitchLib.Communication/Enums/ClientType.cs
+++ b/src/TwitchLib.Communication/Enums/ClientType.cs
@@ -1,8 +1,7 @@
-namespace TwitchLib.Communication.Enums
+namespace TwitchLib.Communication.Enums;
+
+public enum ClientType
{
- public enum ClientType
- {
- Chat,
- PubSub
- }
-}
\ No newline at end of file
+ Chat,
+ PubSub,
+}
diff --git a/src/TwitchLib.Communication/Events/CoreEvents.cs b/src/TwitchLib.Communication/Events/CoreEvents.cs
index 1a356b1..3a31924 100644
--- a/src/TwitchLib.Communication/Events/CoreEvents.cs
+++ b/src/TwitchLib.Communication/Events/CoreEvents.cs
@@ -1,12 +1,8 @@
-using System.Threading.Tasks;
+namespace TwitchLib.Communication.Events;
-namespace TwitchLib.Communication.Events
-{
- /*
- * Custom implementation of asynchronous event handler
- * This is useful to properly and safely handle async Tasks
- * Reference: https://medium.com/@a.lyskawa/the-hitchhiker-guide-to-asynchronous-events-in-c-e9840109fb53
- */
- public delegate Task AsyncEventHandler(object? sender, TEventArgs e);
-
-}
+/*
+* Custom implementation of asynchronous event handler
+* This is useful to properly and safely handle async Tasks
+* Reference: https://medium.com/@a.lyskawa/the-hitchhiker-guide-to-asynchronous-events-in-c-e9840109fb53
+*/
+public delegate Task AsyncEventHandler(object? sender, TEventArgs e);
diff --git a/src/TwitchLib.Communication/Events/OnConnectedEventArgs.cs b/src/TwitchLib.Communication/Events/OnConnectedEventArgs.cs
index 7d96d52..50ddb21 100644
--- a/src/TwitchLib.Communication/Events/OnConnectedEventArgs.cs
+++ b/src/TwitchLib.Communication/Events/OnConnectedEventArgs.cs
@@ -1,6 +1,3 @@
-using System;
+namespace TwitchLib.Communication.Events;
-namespace TwitchLib.Communication.Events
-{
- public class OnConnectedEventArgs : EventArgs { }
-}
+public class OnConnectedEventArgs : EventArgs { }
diff --git a/src/TwitchLib.Communication/Events/OnDisconnectedEventArgs.cs b/src/TwitchLib.Communication/Events/OnDisconnectedEventArgs.cs
index da3b830..bec279e 100644
--- a/src/TwitchLib.Communication/Events/OnDisconnectedEventArgs.cs
+++ b/src/TwitchLib.Communication/Events/OnDisconnectedEventArgs.cs
@@ -1,6 +1,3 @@
-using System;
+namespace TwitchLib.Communication.Events;
-namespace TwitchLib.Communication.Events
-{
- public class OnDisconnectedEventArgs : EventArgs { }
-}
+public class OnDisconnectedEventArgs : EventArgs { }
diff --git a/src/TwitchLib.Communication/Events/OnErrorEventArgs.cs b/src/TwitchLib.Communication/Events/OnErrorEventArgs.cs
index 07752f0..49a3217 100644
--- a/src/TwitchLib.Communication/Events/OnErrorEventArgs.cs
+++ b/src/TwitchLib.Communication/Events/OnErrorEventArgs.cs
@@ -1,14 +1,11 @@
-using System;
+namespace TwitchLib.Communication.Events;
-namespace TwitchLib.Communication.Events
+public class OnErrorEventArgs : EventArgs
{
- public class OnErrorEventArgs : EventArgs
- {
- public Exception Exception { get; }
+ public Exception Exception { get; }
- public OnErrorEventArgs(Exception exception)
- {
- Exception = exception;
- }
+ public OnErrorEventArgs(Exception exception)
+ {
+ Exception = exception;
}
}
diff --git a/src/TwitchLib.Communication/Events/OnFatalErrorEventArgs.cs b/src/TwitchLib.Communication/Events/OnFatalErrorEventArgs.cs
index 4f51fd8..47cf586 100644
--- a/src/TwitchLib.Communication/Events/OnFatalErrorEventArgs.cs
+++ b/src/TwitchLib.Communication/Events/OnFatalErrorEventArgs.cs
@@ -1,19 +1,16 @@
-using System;
+namespace TwitchLib.Communication.Events;
-namespace TwitchLib.Communication.Events
+public class OnFatalErrorEventArgs : EventArgs
{
- public class OnFatalErrorEventArgs : EventArgs
- {
- public string Reason { get; }
+ public string Reason { get; }
- public OnFatalErrorEventArgs(string reason)
- {
- Reason = reason;
- }
+ public OnFatalErrorEventArgs(string reason)
+ {
+ Reason = reason;
+ }
- public OnFatalErrorEventArgs(Exception e)
- {
- Reason = e.ToString();
- }
+ public OnFatalErrorEventArgs(Exception e)
+ {
+ Reason = e.ToString();
}
}
\ No newline at end of file
diff --git a/src/TwitchLib.Communication/Events/OnMessageEventArgs.cs b/src/TwitchLib.Communication/Events/OnMessageEventArgs.cs
index 9fe7282..934b896 100644
--- a/src/TwitchLib.Communication/Events/OnMessageEventArgs.cs
+++ b/src/TwitchLib.Communication/Events/OnMessageEventArgs.cs
@@ -1,14 +1,11 @@
-using System;
+namespace TwitchLib.Communication.Events;
-namespace TwitchLib.Communication.Events
+public class OnMessageEventArgs : EventArgs
{
- public class OnMessageEventArgs : EventArgs
- {
- public string Message { get; }
+ public string Message { get; }
- public OnMessageEventArgs(string message)
- {
- Message = message;
- }
+ public OnMessageEventArgs(string message)
+ {
+ Message = message;
}
}
diff --git a/src/TwitchLib.Communication/Events/OnSendFailedEventArgs.cs b/src/TwitchLib.Communication/Events/OnSendFailedEventArgs.cs
index e1c1db1..51f41d2 100644
--- a/src/TwitchLib.Communication/Events/OnSendFailedEventArgs.cs
+++ b/src/TwitchLib.Communication/Events/OnSendFailedEventArgs.cs
@@ -1,16 +1,14 @@
-using System;
+namespace TwitchLib.Communication.Events;
-namespace TwitchLib.Communication.Events
+public class OnSendFailedEventArgs : EventArgs
{
- public class OnSendFailedEventArgs : EventArgs
- {
- public string Data { get; }
- public Exception Exception { get; }
+ public string Data { get; }
+
+ public Exception Exception { get; }
- public OnSendFailedEventArgs(Exception exception, string data)
- {
- Exception = exception;
- Data = data;
- }
+ public OnSendFailedEventArgs(Exception exception, string data)
+ {
+ Exception = exception;
+ Data = data;
}
}
diff --git a/src/TwitchLib.Communication/Extensions/LogExtensions.cs b/src/TwitchLib.Communication/Extensions/LogExtensions.cs
index 04409ec..8be0a16 100644
--- a/src/TwitchLib.Communication/Extensions/LogExtensions.cs
+++ b/src/TwitchLib.Communication/Extensions/LogExtensions.cs
@@ -1,7 +1,6 @@
#pragma warning disable SYSLIB1006 // Multiple logging methods cannot use the same event id within a class
-using Microsoft.Extensions.Logging;
-using System;
using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Logging;
namespace TwitchLib.Communication.Extensions
{
diff --git a/src/TwitchLib.Communication/Interfaces/IClient.cs b/src/TwitchLib.Communication/Interfaces/IClient.cs
index b5578b3..1edd023 100644
--- a/src/TwitchLib.Communication/Interfaces/IClient.cs
+++ b/src/TwitchLib.Communication/Interfaces/IClient.cs
@@ -1,106 +1,103 @@
-using System;
-using System.Threading.Tasks;
-using TwitchLib.Communication.Events;
+using TwitchLib.Communication.Events;
-namespace TwitchLib.Communication.Interfaces
+namespace TwitchLib.Communication.Interfaces;
+
+public interface IClient : IDisposable
{
- public interface IClient : IDisposable
- {
- ///
- /// The current state of the connection.
- ///
- bool IsConnected { get; }
+ ///
+ /// The current state of the connection.
+ ///
+ bool IsConnected { get; }
- ///
- /// Client Configuration Options
- ///
- IClientOptions Options { get; }
+ ///
+ /// Client Configuration Options
+ ///
+ IClientOptions Options { get; }
- ///
- /// Fires when the Client has connected
- ///
- event AsyncEventHandler? OnConnected;
+ ///
+ /// Fires when the Client has connected
+ ///
+ event AsyncEventHandler? OnConnected;
- ///
- /// Fires when the Client disconnects
- ///
- event AsyncEventHandler? OnDisconnected;
+ ///
+ /// Fires when the Client disconnects
+ ///
+ event AsyncEventHandler? OnDisconnected;
- ///
- /// Fires when An Exception Occurs in the client
- ///
- event AsyncEventHandler? OnError;
+ ///
+ /// Fires when An Exception Occurs in the client
+ ///
+ event AsyncEventHandler? OnError;
- ///
- /// Fires when a Fatal Error Occurs.
- ///
- event AsyncEventHandler? OnFatality;
+ ///
+ /// Fires when a Fatal Error Occurs.
+ ///
+ event AsyncEventHandler? OnFatality;
- ///
- /// Fires when a Message/ group of messages is received.
- ///
- event AsyncEventHandler? OnMessage;
+ ///
+ /// Fires when a Message/ group of messages is received.
+ ///
+ event AsyncEventHandler? OnMessage;
- ///
- /// Fires when a message Send event failed.
- ///
- event AsyncEventHandler? OnSendFailed;
+ ///
+ /// Fires when a message Send event failed.
+ ///
+ event AsyncEventHandler? OnSendFailed;
- ///
- /// Fires when the client reconnects automatically
- ///
- event AsyncEventHandler? OnReconnected;
+ ///
+ /// Fires when the client reconnects automatically
+ ///
+ event AsyncEventHandler? OnReconnected;
- ///
- /// tries to connect to twitch according to !
- ///
- ///
- /// if a connection could be established, otherwise
- ///
- Task OpenAsync();
+ ///
+ /// tries to connect to twitch according to !
+ ///
+ ///
+ /// if a connection could be established, otherwise
+ ///
+ Task OpenAsync();
- ///
- /// if the underlying Client is connected,
- ///
- /// is invoked
- ///
- /// before it makes a call to and
- ///
- ///
- /// this Method is also used by 'TwitchLib.Client.TwitchClient'
- ///
- /// whenever it receives a Reconnect-Message
- ///
- ///
- /// so, if the twitch-servers want us to reconnect,
- ///
- /// we have to close the connection and establish a new ones
- ///
- ///
- /// can also be used for a manual reconnect
- ///
- ///
- /// , if the client reconnected; otherwise
- ///
- Task ReconnectAsync();
+ ///
+ /// if the underlying Client is connected,
+ ///
+ /// is invoked
+ ///
+ /// before it makes a call to and
+ ///
+ ///
+ /// this Method is also used by 'TwitchLib.Client.TwitchClient'
+ ///
+ /// whenever it receives a Reconnect-Message
+ ///
+ ///
+ /// so, if the twitch-servers want us to reconnect,
+ ///
+ /// we have to close the connection and establish a new ones
+ ///
+ ///
+ /// can also be used for a manual reconnect
+ ///
+ ///
+ /// , if the client reconnected; otherwise
+ ///
+ Task ReconnectAsync();
- ///
- /// stops everything
- /// and waits for the via given amount of milliseconds
- ///
- Task CloseAsync();
+ ///
+ /// stops everything
+ /// and waits for the via given amount of milliseconds
+ ///
+ Task CloseAsync();
- ///
- /// Sends the given irc-
- ///
- ///
- /// irc-message to send
- ///
- ///
- /// , if the message was sent
- ///
- /// otherwise
- ///
- Task SendAsync(string message);
- }
-}
\ No newline at end of file
+ ///
+ /// Sends the given irc-
+ ///
+ ///
+ /// irc-message to send
+ ///
+ ///
+ /// , if the message was sent
+ ///
+ /// otherwise
+ ///
+ Task SendAsync(string message);
+}
diff --git a/src/TwitchLib.Communication/Interfaces/IClientOptions.cs b/src/TwitchLib.Communication/Interfaces/IClientOptions.cs
index fc8a049..e845484 100644
--- a/src/TwitchLib.Communication/Interfaces/IClientOptions.cs
+++ b/src/TwitchLib.Communication/Interfaces/IClientOptions.cs
@@ -1,29 +1,28 @@
using TwitchLib.Communication.Enums;
using TwitchLib.Communication.Models;
-namespace TwitchLib.Communication.Interfaces
+namespace TwitchLib.Communication.Interfaces;
+
+public interface IClientOptions
{
- public interface IClientOptions
- {
- ///
- /// Type of the Client to Create. Possible Types Chat or PubSub.
- ///
- ClientType ClientType { get; }
+ ///
+ /// Type of the Client to Create. Possible Types Chat or PubSub.
+ ///
+ ClientType ClientType { get; }
- ///
- /// How long to wait on a clean disconnect [in ms] (default 1_500ms).
- ///
- uint DisconnectWait { get; }
+ ///
+ /// How long to wait on a clean disconnect [in ms] (default 1_500ms).
+ ///
+ uint DisconnectWait { get; }
- ///
- /// Reconnection Policy Settings. Reconnect without Losing data etc.
- /// The Default Policy applied is 10 reconnection attempts with 3 seconds between each attempt.
- ///
- ReconnectionPolicy ReconnectionPolicy { get; }
+ ///
+ /// Reconnection Policy Settings. Reconnect without Losing data etc.
+ /// The Default Policy applied is 10 reconnection attempts with 3 seconds between each attempt.
+ ///
+ ReconnectionPolicy ReconnectionPolicy { get; }
- ///
- /// Use Secure Connection [SSL] (default: true)
- ///
- bool UseSsl { get; }
- }
-}
\ No newline at end of file
+ ///
+ /// Use Secure Connection [SSL] (default: true)
+ ///
+ bool UseSsl { get; }
+}
diff --git a/src/TwitchLib.Communication/Models/ClientOptions.cs b/src/TwitchLib.Communication/Models/ClientOptions.cs
index c97dd89..0741bf3 100644
--- a/src/TwitchLib.Communication/Models/ClientOptions.cs
+++ b/src/TwitchLib.Communication/Models/ClientOptions.cs
@@ -1,41 +1,47 @@
using TwitchLib.Communication.Enums;
using TwitchLib.Communication.Interfaces;
-namespace TwitchLib.Communication.Models
+namespace TwitchLib.Communication.Models;
+
+public class ClientOptions : IClientOptions
{
- public class ClientOptions : IClientOptions
- {
- public ReconnectionPolicy ReconnectionPolicy { get; }
- public bool UseSsl { get; }
- public uint DisconnectWait { get; }
- public ClientType ClientType { get; }
+ ///
+ public ReconnectionPolicy ReconnectionPolicy { get; }
+
+ ///
+ public bool UseSsl { get; }
- ///
- ///
- ///
- /// your own
- ///
- /// by leaving it , a , that makes every 3_000ms one attempt to connect for ten times, is going to be applied
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public ClientOptions(
- ReconnectionPolicy? reconnectionPolicy = null,
- bool useSsl = true,
- uint disconnectWait = 1_500,
- ClientType clientType = ClientType.Chat)
- {
- ReconnectionPolicy = reconnectionPolicy ?? new ReconnectionPolicy(3_000, maxAttempts: 10);
- UseSsl = useSsl;
- DisconnectWait = disconnectWait;
- ClientType = clientType;
- }
+ ///
+ public uint DisconnectWait { get; }
+
+ ///
+ public ClientType ClientType { get; }
+
+ ///
+ ///
+ ///
+ /// your own
+ ///
+ /// by leaving it , a , that makes every 3_000ms one attempt to connect for ten times, is going to be applied
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public ClientOptions(
+ ReconnectionPolicy? reconnectionPolicy = null,
+ bool useSsl = true,
+ uint disconnectWait = 1_500,
+ ClientType clientType = ClientType.Chat)
+ {
+ ReconnectionPolicy = reconnectionPolicy ?? new ReconnectionPolicy(3_000, maxAttempts: 10);
+ UseSsl = useSsl;
+ DisconnectWait = disconnectWait;
+ ClientType = clientType;
}
}
\ No newline at end of file
diff --git a/src/TwitchLib.Communication/Models/NoReconnectionPolicy.cs b/src/TwitchLib.Communication/Models/NoReconnectionPolicy.cs
index 63e3bfd..70b4df1 100644
--- a/src/TwitchLib.Communication/Models/NoReconnectionPolicy.cs
+++ b/src/TwitchLib.Communication/Models/NoReconnectionPolicy.cs
@@ -1,15 +1,14 @@
-namespace TwitchLib.Communication.Models
+namespace TwitchLib.Communication.Models;
+
+///
+/// This policy should be used to omit reconnect-attempts.
+///
+public class NoReconnectionPolicy : ReconnectionPolicy
{
- ///
- /// This policy should be used to omit reconnect-attempts.
- ///
- public class NoReconnectionPolicy : ReconnectionPolicy
+ public NoReconnectionPolicy()
+ : base(
+ reconnectInterval: 0,
+ maxAttempts: 1)
{
- public NoReconnectionPolicy()
- : base(
- reconnectInterval: 0,
- maxAttempts: 1)
- {
- }
}
-}
\ No newline at end of file
+}
diff --git a/src/TwitchLib.Communication/Models/ReconnectionPolicy.cs b/src/TwitchLib.Communication/Models/ReconnectionPolicy.cs
index b81a5cd..3bb1a2d 100644
--- a/src/TwitchLib.Communication/Models/ReconnectionPolicy.cs
+++ b/src/TwitchLib.Communication/Models/ReconnectionPolicy.cs
@@ -1,198 +1,197 @@
-namespace TwitchLib.Communication.Models
+namespace TwitchLib.Communication.Models;
+
+///
+/// Connection/Reconnection-Policy
+///
+///
+/// controls the attempts to make to connect and to reconnect to twitch
+///
+///
+/// to omit reconnects and to only make one attempt to connect to twitch, please use
+///
+public class ReconnectionPolicy
{
+ private readonly int _reconnectStepInterval;
+ private readonly int? _initMaxAttempts;
+ private int _currentReconnectInterval;
+ private readonly int _maxReconnectInterval;
+ private int? _maxAttempts;
+ private int _attemptsMade;
+
///
- /// Connection/Reconnection-Policy
+ /// the or
+ /// infinitely
+ /// attempts to reconnect
+ ///
+ ///
+ /// with each attempt, the reconnect interval increases by 3_000 milliseconds
+ /// until it reaches 30_000 milliseconds
///
+ ///
///
- /// controls the attempts to make to connect and to reconnect to twitch
///
+ /// Example:
///
- /// to omit reconnects and to only make one attempt to connect to twitch, please use
+ /// try to connect -> couldn't connect -> wait 3_000 milliseconds -> try to connect -> couldn't connect -> wait 6_000 milliseconds -> and so on
///
- public class ReconnectionPolicy
+ public ReconnectionPolicy()
{
- private readonly int _reconnectStepInterval;
- private readonly int? _initMaxAttempts;
- private int _currentReconnectInterval;
- private readonly int _maxReconnectInterval;
- private int? _maxAttempts;
- private int _attemptsMade;
+ _reconnectStepInterval = 3_000;
+ _currentReconnectInterval = _reconnectStepInterval;
+ _maxReconnectInterval = 30_000;
+ _maxAttempts = null;
+ _initMaxAttempts = null;
+ _attemptsMade = 0;
+ }
- ///
- /// the or
- /// infinitely
- /// attempts to reconnect
- ///
- ///
- /// with each attempt, the reconnect interval increases by 3_000 milliseconds
- /// until it reaches 30_000 milliseconds
- ///
- ///
- ///
- ///
- /// Example:
- ///
- /// try to connect -> couldn't connect -> wait 3_000 milliseconds -> try to connect -> couldn't connect -> wait 6_000 milliseconds -> and so on
- ///
- public ReconnectionPolicy()
- {
- _reconnectStepInterval = 3_000;
- _currentReconnectInterval = _reconnectStepInterval;
- _maxReconnectInterval = 30_000;
- _maxAttempts = null;
- _initMaxAttempts = null;
- _attemptsMade = 0;
- }
+ ///
+ /// the or
+ /// attempts to reconnect for times
+ ///
+ ///
+ /// with each attempt, the reconnect interval increases by the amount of
+ /// until it reaches
+ ///
+ ///
+ /// Example:
+ ///
+ /// = 3_000
+ ///
+ /// = 30_000
+ ///
+ /// try to connect -> couldnt connect -> wait 3_000 milliseconds -> try to connect -> couldnt connect -> wait 6_000 milliseconds -> and so on
+ ///
+ ///
+ /// minimum interval in milliseconds
+ ///
+ ///
+ /// maximum interval in milliseconds
+ ///
+ ///
+ /// means infinite; it never stops to try to reconnect
+ ///
+ public ReconnectionPolicy(
+ int minReconnectInterval,
+ int maxReconnectInterval,
+ int maxAttempts)
+ {
+ _reconnectStepInterval = minReconnectInterval;
+ _currentReconnectInterval = minReconnectInterval > maxReconnectInterval
+ ? maxReconnectInterval
+ : minReconnectInterval;
+ _maxReconnectInterval = maxReconnectInterval;
+ _maxAttempts = maxAttempts;
+ _initMaxAttempts = maxAttempts;
+ _attemptsMade = 0;
+ }
- ///
- /// the or
- /// attempts to reconnect for times
- ///
- ///
- /// with each attempt, the reconnect interval increases by the amount of
- /// until it reaches
- ///
- ///
- /// Example:
- ///
- /// = 3_000
- ///
- /// = 30_000
- ///
- /// try to connect -> couldnt connect -> wait 3_000 milliseconds -> try to connect -> couldnt connect -> wait 6_000 milliseconds -> and so on
- ///
- ///
- /// minimum interval in milliseconds
- ///
- ///
- /// maximum interval in milliseconds
- ///
- ///
- /// means infinite; it never stops to try to reconnect
- ///
- public ReconnectionPolicy(
- int minReconnectInterval,
- int maxReconnectInterval,
- int maxAttempts)
- {
- _reconnectStepInterval = minReconnectInterval;
- _currentReconnectInterval = minReconnectInterval > maxReconnectInterval
- ? maxReconnectInterval
- : minReconnectInterval;
- _maxReconnectInterval = maxReconnectInterval;
- _maxAttempts = maxAttempts;
- _initMaxAttempts = maxAttempts;
- _attemptsMade = 0;
- }
+ ///
+ /// the or
+ /// infinitely
+ /// attempts to reconnect
+ ///
+ ///
+ /// with each attempt, the reconnect interval increases by the amount of
+ /// until it reaches
+ ///
+ ///
+ /// Example:
+ ///
+ /// = 3_000
+ ///
+ /// = 30_000
+ ///
+ /// try to connect -> couldn't connect -> wait 3_000 milliseconds -> try to connect -> couldn't connect -> wait 6_000 milliseconds -> and so on
+ ///
+ ///
+ /// minimum interval in milliseconds
+ ///
+ ///
+ /// maximum interval in milliseconds
+ ///
+ public ReconnectionPolicy(
+ int minReconnectInterval,
+ int maxReconnectInterval)
+ {
+ _reconnectStepInterval = minReconnectInterval;
+ _currentReconnectInterval = minReconnectInterval > maxReconnectInterval
+ ? maxReconnectInterval
+ : minReconnectInterval;
+ _maxReconnectInterval = maxReconnectInterval;
+ _maxAttempts = null;
+ _initMaxAttempts = null;
+ _attemptsMade = 0;
+ }
- ///
- /// the or
- /// infinitely
- /// attempts to reconnect
- ///
- ///
- /// with each attempt, the reconnect interval increases by the amount of
- /// until it reaches
- ///
- ///
- /// Example:
- ///
- /// = 3_000
- ///
- /// = 30_000
- ///
- /// try to connect -> couldn't connect -> wait 3_000 milliseconds -> try to connect -> couldn't connect -> wait 6_000 milliseconds -> and so on
- ///
- ///
- /// minimum interval in milliseconds
- ///
- ///
- /// maximum interval in milliseconds
- ///
- public ReconnectionPolicy(
- int minReconnectInterval,
- int maxReconnectInterval)
- {
- _reconnectStepInterval = minReconnectInterval;
- _currentReconnectInterval = minReconnectInterval > maxReconnectInterval
- ? maxReconnectInterval
- : minReconnectInterval;
- _maxReconnectInterval = maxReconnectInterval;
- _maxAttempts = null;
- _initMaxAttempts = null;
- _attemptsMade = 0;
- }
+ ///
+ /// the or
+ /// infinitely
+ /// attempts to reconnect every -milliseconds
+ ///
+ ///
+ /// Interval in milliseconds between trying to reconnect
+ ///
+ public ReconnectionPolicy(int reconnectInterval)
+ {
+ _reconnectStepInterval = reconnectInterval;
+ _currentReconnectInterval = reconnectInterval;
+ _maxReconnectInterval = reconnectInterval;
+ _maxAttempts = null;
+ _initMaxAttempts = null;
+ _attemptsMade = 0;
+ }
- ///
- /// the or
- /// infinitely
- /// attempts to reconnect every -milliseconds
- ///
- ///
- /// Interval in milliseconds between trying to reconnect
- ///
- public ReconnectionPolicy(int reconnectInterval)
- {
- _reconnectStepInterval = reconnectInterval;
- _currentReconnectInterval = reconnectInterval;
- _maxReconnectInterval = reconnectInterval;
- _maxAttempts = null;
- _initMaxAttempts = null;
- _attemptsMade = 0;
- }
+ ///
+ /// the or
+ /// attempts to reconnect every -milliseconds for times
+ ///
+ ///
+ /// Interval in milliseconds between trying to reconnect
+ ///
+ ///
+ /// means infinite; it never stops to try to reconnect
+ ///
+ public ReconnectionPolicy(
+ int reconnectInterval,
+ int? maxAttempts)
+ {
+ _reconnectStepInterval = reconnectInterval;
+ _currentReconnectInterval = reconnectInterval;
+ _maxReconnectInterval = reconnectInterval;
+ _maxAttempts = maxAttempts;
+ _initMaxAttempts = maxAttempts;
+ _attemptsMade = 0;
+ }
- ///
- /// the or
- /// attempts to reconnect every -milliseconds for times
- ///
- ///
- /// Interval in milliseconds between trying to reconnect
- ///
- ///
- /// means infinite; it never stops to try to reconnect
- ///
- public ReconnectionPolicy(
- int reconnectInterval,
- int? maxAttempts)
- {
- _reconnectStepInterval = reconnectInterval;
- _currentReconnectInterval = reconnectInterval;
- _maxReconnectInterval = reconnectInterval;
- _maxAttempts = maxAttempts;
- _initMaxAttempts = maxAttempts;
- _attemptsMade = 0;
- }
+ internal void Reset(bool isReconnect)
+ {
+ if (isReconnect) return;
+ _attemptsMade = 0;
+ _currentReconnectInterval = _reconnectStepInterval;
+ _maxAttempts = _initMaxAttempts;
+ }
- internal void Reset(bool isReconnect)
+ internal void ProcessValues()
+ {
+ _attemptsMade++;
+ if (_currentReconnectInterval < _maxReconnectInterval)
{
- if (isReconnect) return;
- _attemptsMade = 0;
- _currentReconnectInterval = _reconnectStepInterval;
- _maxAttempts = _initMaxAttempts;
+ _currentReconnectInterval += _reconnectStepInterval;
}
- internal void ProcessValues()
+ if (_currentReconnectInterval > _maxReconnectInterval)
{
- _attemptsMade++;
- if (_currentReconnectInterval < _maxReconnectInterval)
- {
- _currentReconnectInterval += _reconnectStepInterval;
- }
-
- if (_currentReconnectInterval > _maxReconnectInterval)
- {
- _currentReconnectInterval = _maxReconnectInterval;
- }
+ _currentReconnectInterval = _maxReconnectInterval;
}
+ }
- public int GetReconnectInterval()
- {
- return _currentReconnectInterval;
- }
+ public int GetReconnectInterval()
+ {
+ return _currentReconnectInterval;
+ }
- public bool AreAttemptsComplete()
- {
- return _attemptsMade == _maxAttempts;
- }
+ public bool AreAttemptsComplete()
+ {
+ return _attemptsMade == _maxAttempts;
}
-}
\ No newline at end of file
+}
diff --git a/src/TwitchLib.Communication/Services/ConnectionWatchDog.cs b/src/TwitchLib.Communication/Services/ConnectionWatchDog.cs
index 5d4e28b..9776b29 100644
--- a/src/TwitchLib.Communication/Services/ConnectionWatchDog.cs
+++ b/src/TwitchLib.Communication/Services/ConnectionWatchDog.cs
@@ -1,131 +1,127 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using TwitchLib.Communication.Clients;
using TwitchLib.Communication.Events;
using TwitchLib.Communication.Extensions;
-namespace TwitchLib.Communication.Services
+namespace TwitchLib.Communication.Services;
+
+///
+/// Service that checks connection state.
+///
+internal class ConnectionWatchDog where T : IDisposable
{
+ private readonly ILogger? _logger;
+ private readonly ClientBase _client;
+
///
- /// Service that checks connection state.
+ ///
+ /// -
+ /// should only be set to a new instance in
+ ///
+ /// -
+ /// should only be set to in
+ ///
+ ///
///
- internal class ConnectionWatchDog where T : IDisposable
- {
- private readonly ILogger? _logger;
- private readonly ClientBase _client;
+ private CancellationTokenSource? _cancellationTokenSource;
- ///
- ///
- /// -
- /// should only be set to a new instance in
- ///
- /// -
- /// should only be set to in
- ///
- ///
- ///
- private CancellationTokenSource? _cancellationTokenSource;
+ private const int MonitorTaskDelayInMilliseconds = 200;
- private const int MonitorTaskDelayInMilliseconds = 200;
-
- public bool IsRunning { get; private set; }
+ public bool IsRunning { get; private set; }
- internal ConnectionWatchDog(
- ClientBase client,
- ILogger? logger = null)
- {
- _logger = logger;
- _client = client;
- }
+ internal ConnectionWatchDog(
+ ClientBase client,
+ ILogger? logger = null)
+ {
+ _logger = logger;
+ _client = client;
+ }
- internal Task StartMonitorTaskAsync()
+ internal Task StartMonitorTaskAsync()
+ {
+ _logger?.TraceMethodCall(GetType());
+ // We dont want to start more than one WatchDog
+ if (_cancellationTokenSource != null)
{
- _logger?.TraceMethodCall(GetType());
- // We dont want to start more than one WatchDog
- if (_cancellationTokenSource != null)
- {
- Exception ex = new InvalidOperationException("Monitor Task cant be started more than once!");
- _logger?.LogExceptionAsError(GetType(), ex);
- throw ex;
- }
+ Exception ex = new InvalidOperationException("Monitor Task cant be started more than once!");
+ _logger?.LogExceptionAsError(GetType(), ex);
+ throw ex;
+ }
- // This should be the only place where a new instance of CancellationTokenSource is set
- _cancellationTokenSource = new CancellationTokenSource();
+ // This should be the only place where a new instance of CancellationTokenSource is set
+ _cancellationTokenSource = new CancellationTokenSource();
- IsRunning = true;
- return Task.Run(MonitorTaskActionAsync, _cancellationTokenSource.Token);
- }
+ IsRunning = true;
+ return Task.Run(MonitorTaskActionAsync, _cancellationTokenSource.Token);
+ }
- internal async Task StopAsync()
- {
- IsRunning = false;
- _logger?.TraceMethodCall(GetType());
- _cancellationTokenSource?.Cancel();
- // give MonitorTaskAction a chance to catch cancellation
- // otherwise it may result in an Exception
- await Task.Delay(MonitorTaskDelayInMilliseconds * 2);
- _cancellationTokenSource?.Dispose();
- // set it to null for the check within this.StartMonitorTask()
- _cancellationTokenSource = null;
- }
+ internal async Task StopAsync()
+ {
+ IsRunning = false;
+ _logger?.TraceMethodCall(GetType());
+ _cancellationTokenSource?.Cancel();
+ // give MonitorTaskAction a chance to catch cancellation
+ // otherwise it may result in an Exception
+ await Task.Delay(MonitorTaskDelayInMilliseconds * 2);
+ _cancellationTokenSource?.Dispose();
+ // set it to null for the check within this.StartMonitorTask()
+ _cancellationTokenSource = null;
+ }
- private async Task MonitorTaskActionAsync()
+ private async Task MonitorTaskActionAsync()
+ {
+ _logger?.TraceMethodCall(GetType());
+ try
{
- _logger?.TraceMethodCall(GetType());
- try
+ while (_cancellationTokenSource != null &&
+ !_cancellationTokenSource.Token.IsCancellationRequested)
{
- while (_cancellationTokenSource != null &&
- !_cancellationTokenSource.Token.IsCancellationRequested)
+ // we expect the client is connected,
+ // when this monitor task starts
+ // cause BaseClient.Open() starts NetworkServices after a connection could be established
+ if (!_client.IsConnected)
{
- // we expect the client is connected,
- // when this monitor task starts
- // cause BaseClient.Open() starts NetworkServices after a connection could be established
- if (!_client.IsConnected)
- {
- _logger?.TraceAction(GetType(), "Client isn't connected anymore");
- // no call to close needed,
- // ReconnectInternal() calls the correct Close-Method within the Client
- // ReconnectInternal() makes attempts to reconnect according to the ReconnectionPolicy within the IClientOptions
- _logger?.TraceAction(GetType(), "Try to reconnect");
-
- var connected = await _client.ReconnectInternalAsync();
- if (!connected)
- {
- _logger?.TraceAction(GetType(), "Client couldn't reconnect");
- // if the ReconnectionPolicy is set up to be finite
- // and no connection could be established
- // a call to Client.Close() is made
- // that public Close() also shuts down this ConnectionWatchDog
- await _client.CloseAsync();
- break;
- }
+ _logger?.TraceAction(GetType(), "Client isn't connected anymore");
+ // no call to close needed,
+ // ReconnectInternal() calls the correct Close-Method within the Client
+ // ReconnectInternal() makes attempts to reconnect according to the ReconnectionPolicy within the IClientOptions
+ _logger?.TraceAction(GetType(), "Try to reconnect");
- _logger?.TraceAction(GetType(), "Client reconnected");
+ var connected = await _client.ReconnectInternalAsync();
+ if (!connected)
+ {
+ _logger?.TraceAction(GetType(), "Client couldn't reconnect");
+ // if the ReconnectionPolicy is set up to be finite
+ // and no connection could be established
+ // a call to Client.Close() is made
+ // that public Close() also shuts down this ConnectionWatchDog
+ await _client.CloseAsync();
+ break;
}
- await Task.Delay(MonitorTaskDelayInMilliseconds);
+ _logger?.TraceAction(GetType(), "Client reconnected");
}
- }
- catch (TaskCanceledException _)
- {
- // Swallow any cancellation exceptions
- }
- catch (OperationCanceledException ex)
- {
- // Occurs if the Tasks are canceled by the CancellationTokenSource.Token
- _logger?.LogExceptionAsInformation(GetType(), ex);
- }
- catch (Exception ex)
- {
- _logger?.LogExceptionAsError(GetType(), ex);
- await _client.RaiseError(new OnErrorEventArgs(ex));
- await _client.RaiseFatal();
- // To ensure CancellationTokenSource is set to null again call Stop();
- await StopAsync();
+ await Task.Delay(MonitorTaskDelayInMilliseconds);
}
}
+ catch (TaskCanceledException)
+ {
+ // Swallow any cancellation exceptions
+ }
+ catch (OperationCanceledException ex)
+ {
+ // Occurs if the Tasks are canceled by the CancellationTokenSource.Token
+ _logger?.LogExceptionAsInformation(GetType(), ex);
+ }
+ catch (Exception ex)
+ {
+ _logger?.LogExceptionAsError(GetType(), ex);
+ await _client.RaiseError(new OnErrorEventArgs(ex));
+ await _client.RaiseFatal();
+
+ // To ensure CancellationTokenSource is set to null again call Stop();
+ await StopAsync();
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/TwitchLib.Communication/Services/NetworkServices.cs b/src/TwitchLib.Communication/Services/NetworkServices.cs
index 917df0c..70869ae 100644
--- a/src/TwitchLib.Communication/Services/NetworkServices.cs
+++ b/src/TwitchLib.Communication/Services/NetworkServices.cs
@@ -1,55 +1,51 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging;
using TwitchLib.Communication.Clients;
using TwitchLib.Communication.Extensions;
-namespace TwitchLib.Communication.Services
+namespace TwitchLib.Communication.Services;
+
+///
+/// to bundle Network-Service-s
+///
+internal class NetworkServices where T : IDisposable
{
- ///
- /// to bundle Network-Service-s
- ///
- internal class NetworkServices where T : IDisposable
- {
- private Task? _listenTask;
- private Task? _monitorTask;
- private readonly ClientBase _client;
- private readonly ILogger? _logger;
- private readonly ConnectionWatchDog _connectionWatchDog;
+ private Task? _listenTask;
+ private Task? _monitorTask;
+ private readonly ClientBase _client;
+ private readonly ILogger? _logger;
+ private readonly ConnectionWatchDog _connectionWatchDog;
- private CancellationToken Token => _client.Token;
+ private CancellationToken Token => _client.Token;
- internal NetworkServices(
- ClientBase client,
- ILogger? logger = null)
- {
- _logger = logger;
- _client = client;
- _connectionWatchDog = new ConnectionWatchDog(_client, logger);
- }
+ internal NetworkServices(
+ ClientBase client,
+ ILogger? logger = null)
+ {
+ _logger = logger;
+ _client = client;
+ _connectionWatchDog = new ConnectionWatchDog(_client, logger);
+ }
- internal void Start()
+ internal void Start()
+ {
+ _logger?.TraceMethodCall(GetType());
+ if (_monitorTask == null || !_connectionWatchDog.IsRunning)
{
- _logger?.TraceMethodCall(GetType());
- if (_monitorTask == null || !_connectionWatchDog.IsRunning)
- {
- // this task is probably still running
- // may be in case of a network connection loss
- // all other Tasks haven't been started or have been canceled!
- // ConnectionWatchDog is the only one, that has a separate CancellationTokenSource!
-
- // Let those tasks run in the background, do not await them
- _monitorTask = _connectionWatchDog.StartMonitorTaskAsync();
- }
-
- _listenTask = Task.Run(_client.ListenTaskActionAsync, Token);
+ // this task is probably still running
+ // may be in case of a network connection loss
+ // all other Tasks haven't been started or have been canceled!
+ // ConnectionWatchDog is the only one, that has a separate CancellationTokenSource!
+
+ // Let those tasks run in the background, do not await them
+ _monitorTask = _connectionWatchDog.StartMonitorTaskAsync();
}
- internal async Task StopAsync()
- {
- _logger?.TraceMethodCall(GetType());
- await _connectionWatchDog.StopAsync();
- }
+ _listenTask = Task.Run(_client.ListenTaskActionAsync, Token);
+ }
+
+ internal async Task StopAsync()
+ {
+ _logger?.TraceMethodCall(GetType());
+ await _connectionWatchDog.StopAsync();
}
-}
\ No newline at end of file
+}
diff --git a/src/TwitchLib.Communication/TwitchLib.Communication.csproj b/src/TwitchLib.Communication/TwitchLib.Communication.csproj
index 984a9f5..b59d546 100644
--- a/src/TwitchLib.Communication/TwitchLib.Communication.csproj
+++ b/src/TwitchLib.Communication/TwitchLib.Communication.csproj
@@ -3,6 +3,7 @@
netstandard2.0;netstandard2.1;net6.0;net7.0
enable
+ enable
latest
2.0.0
$(VersionSuffix)
@@ -22,6 +23,7 @@
2.0.0
true
True
+ nullable