From 8326af61683b11d0cd60f589f85fa6288d2a7b82 Mon Sep 17 00:00:00 2001 From: Jeesu Choi Date: Sat, 15 Jun 2024 09:45:56 +0900 Subject: [PATCH] Revert "Revert "Apply code style"" --- .editorconfig | 99 ++++- .vscode/settings.json | 5 + Directory.Build.props | 26 ++ Menees.Analyzers.Settings.xml | 5 + .../ClientContext.cs | 29 +- src/JSSoft.Communication.Client/Program.cs | 25 +- .../Services/DataService.cs | 33 +- .../Services/UserService.cs | 115 +++--- src/JSSoft.Communication.Server/Program.cs | 25 +- .../ServerContext.cs | 27 +- .../Services/DataService.cs | 53 +-- .../Services/User.cs | 22 ++ .../Services/UserInfo.cs | 38 -- .../Services/UserService.cs | 322 ++++++++------- src/JSSoft.Communication/AdaptorProvider.cs | 45 +-- src/JSSoft.Communication/ClientContext.cs | 27 +- src/JSSoft.Communication/ClientService.cs | 42 +- src/JSSoft.Communication/EndPointUtility.cs | 32 +- .../Extensions/ISerializerExtensions.cs | 34 +- .../Extensions/IServiceContextExtensions.cs | 30 +- .../Grpc/AdaptorClient.cs | 369 +++++++++++------- .../Grpc/AdaptorClientImpl.cs | 38 +- .../Grpc/AdaptorServer.cs | 224 ++++++----- .../Grpc/AdaptorServerImpl.cs | 34 +- src/JSSoft.Communication/Grpc/CallbackData.cs | 8 + src/JSSoft.Communication/Grpc/Peer.cs | 34 +- .../Grpc/PeerCollection.cs | 33 +- src/JSSoft.Communication/IAdaptor.cs | 39 +- src/JSSoft.Communication/IAdaptorProvider.cs | 32 +- src/JSSoft.Communication/IInstanceContext.cs | 25 +- .../ILGeneratorExtensions.cs | 44 +-- src/JSSoft.Communication/IPeer.cs | 25 +- src/JSSoft.Communication/ISerializer.cs | 25 +- .../ISerializerProvider.cs | 29 +- src/JSSoft.Communication/IService.cs | 25 +- src/JSSoft.Communication/IServiceContext.cs | 45 +-- src/JSSoft.Communication/InstanceBase.cs | 167 +++++--- .../InstanceCollection.cs | 27 +- src/JSSoft.Communication/InstanceContext.cs | 39 +- .../InstanceMethodAttribute.cs | 25 +- src/JSSoft.Communication/InvokeOptions.cs | 19 + src/JSSoft.Communication/InvokeResult.cs | 17 + src/JSSoft.Communication/JsonSerializer.cs | 35 +- .../JsonSerializerProvider.cs | 33 +- .../Logging/ConsoleLogger.cs | 29 +- .../Logging/EmptyLogger.cs | 29 +- src/JSSoft.Communication/Logging/ILogger.cs | 25 +- .../Logging/ILoggerExtensions.cs | 25 +- src/JSSoft.Communication/Logging/LogLevel.cs | 25 +- .../Logging/LogUtility.cs | 39 +- .../Logging/TraceLogger.cs | 31 +- src/JSSoft.Communication/MethodDescriptor.cs | 130 +++--- .../MethodDescriptorCollection.cs | 31 +- src/JSSoft.Communication/MethodUtility.cs | 52 ++- src/JSSoft.Communication/PeerDescriptor.cs | 35 +- src/JSSoft.Communication/ServerContext.cs | 25 +- src/JSSoft.Communication/ServerService.cs | 44 +-- src/JSSoft.Communication/ServiceAttribute.cs | 27 +- src/JSSoft.Communication/ServiceBase.cs | 35 +- src/JSSoft.Communication/ServiceCollection.cs | 41 +- .../ServiceContextAttribute.cs | 27 +- .../ServiceContextBase.cs | 188 +++++---- .../ServiceContractAttribute.cs | 25 +- .../ServiceInstanceBuilder.cs | 144 +++---- src/JSSoft.Communication/ServiceState.cs | 25 +- src/JSSoft.Communication/ServiceToken.cs | 32 +- src/JSSoft.Communication/ServiceUtility.cs | 68 ++-- src/JSSoft.Communication/TaskUtility.cs | 30 +- .../Threading/Dispatcher.cs | 112 ++---- .../Threading/DispatcherScheduler.cs | 73 ++-- .../DispatcherSynchronizationContext.cs | 30 ++ .../UnreachableException.cs | 25 +- .../Application.cs | 274 +++++++------ .../ApplicationOptions.cs | 29 +- .../CommandContext.cs | 37 +- .../Commands/CloseCommand.cs | 39 +- .../Commands/DataCommand.cs | 42 +- .../Commands/ExitCommand.cs | 38 +- .../Commands/HelpCommand.cs | 7 +- .../Commands/LoginCommand.cs | 50 +-- .../Commands/LogoutCommand.cs | 37 +- .../Commands/OpenCommand.cs | 35 +- .../Commands/UserCommand.cs | 105 +++-- .../Commands/VersionCommand.cs | 7 +- .../IApplication.cs | 31 +- .../SystemTerminal.cs | 39 +- .../JSSoft.Communication.Services.projitems | 10 +- .../Services/Authority.cs | 38 +- .../Services/IDataService.cs | 25 +- .../Services/INotifyUserService.cs | 27 +- .../Services/IUserCallback.cs | 37 +- .../Services/IUserService.cs | 41 +- .../Services/UserAuthorityEventArgs.cs | 29 +- .../Services/UserAuthorityOptions.cs | 17 + .../Services/UserCreateOptions.cs | 19 + .../Services/UserDeleteOptions.cs | 15 + .../Services/UserEventArgs.cs | 31 +- .../Services/UserInfo.cs | 15 + .../Services/UserLoginOptions.cs | 13 + .../Services/UserMessageEventArgs.cs | 25 +- .../Services/UserNameEventArgs.cs | 29 +- .../Services/UserRenameOptions.cs | 15 + .../Services/UserSendMessageOptions.cs | 17 + stylecop.json | 18 + .../CallbackTest.cs | 107 +++-- .../ClientContextTest.cs | 73 ++-- .../ClientTestBase.cs | 44 +-- .../DispatcherTest.cs | 46 +-- .../EventTestUtility.cs | 36 +- .../Exceptions/ArgumentExceptionTest.cs | 25 +- .../Exceptions/ArgumentNullExceptionTest.cs | 25 +- .../ArgumentOutOfRangeExceptionTest.cs | 25 +- .../Exceptions/ExceptionTestBase.cs | 57 ++- .../IndexOutOfRangeExceptionTest.cs | 25 +- .../InvalidOperationExceptionTest.cs | 25 +- .../Exceptions/NotSupportedExceptionTest.cs | 25 +- .../Exceptions/NullReferenceExceptionTest.cs | 25 +- .../Exceptions/ObjectDisposedExceptionTest.cs | 25 +- .../Extensions/ServiceContextExtensions.cs | 29 +- .../GlobalUsings.cs | 27 +- test/JSSoft.Communication.Tests/InvokeTest.cs | 80 ++-- .../RandomEndPoint.cs | 33 +- .../ServerContextTest.cs | 66 +--- test/JSSoft.Communication.Tests/TestLogger.cs | 25 +- test/JSSoft.Communication.Tests/UnitTest1.cs | 81 ++-- 125 files changed, 2501 insertions(+), 3421 deletions(-) create mode 100644 Menees.Analyzers.Settings.xml create mode 100644 src/JSSoft.Communication.Server/Services/User.cs delete mode 100644 src/JSSoft.Communication.Server/Services/UserInfo.cs create mode 100644 src/JSSoft.Communication/Grpc/CallbackData.cs create mode 100644 src/JSSoft.Communication/InvokeOptions.cs create mode 100644 src/JSSoft.Communication/InvokeResult.cs create mode 100644 src/JSSoft.Communication/Threading/DispatcherSynchronizationContext.cs create mode 100644 src/Sharing/JSSoft.Communication.Services/Services/UserAuthorityOptions.cs create mode 100644 src/Sharing/JSSoft.Communication.Services/Services/UserCreateOptions.cs create mode 100644 src/Sharing/JSSoft.Communication.Services/Services/UserDeleteOptions.cs create mode 100644 src/Sharing/JSSoft.Communication.Services/Services/UserInfo.cs create mode 100644 src/Sharing/JSSoft.Communication.Services/Services/UserLoginOptions.cs create mode 100644 src/Sharing/JSSoft.Communication.Services/Services/UserRenameOptions.cs create mode 100644 src/Sharing/JSSoft.Communication.Services/Services/UserSendMessageOptions.cs create mode 100644 stylecop.json diff --git a/.editorconfig b/.editorconfig index 879a997..5b17aa8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,28 +1,89 @@ -[*.{cs,vb}] +# https://editorconfig.org/ +root = true -# IDE0060: 사용하지 않는 매개 변수를 제거하세요. -dotnet_code_quality_unused_parameters = non_public:none +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +tab_width = 8 +trim_trailing_whitespace = true +max_line_length = 80 +indent_style = space +continuation_indent_size = 4 +indent_size = 4 -# IDE0003: 한정자 제거 -dotnet_style_qualification_for_event = false:none +[*.{json,ps1,sh,yaml,yml}] +indent_size = 2 +continuation_indent_size = 2 -# IDE0003: 한정자 제거 -dotnet_style_qualification_for_field = false:none +[*.{csproj,xml}] +indent_size = 2 +quote_type = double -# IDE0003: 한정자 제거 -dotnet_style_qualification_for_method = false:none +[*.cs] +max_line_length = 100 +curly_bracket_next_line = true +spaces_around_operators = true +indent_brace_style = Allman +dotnet_naming_rule.public_members_must_be_capitalized.symbols = public_symbols +dotnet_naming_symbols.public_symbols.applicable_kinds = property,method,field,event,delegate +dotnet_naming_symbols.public_symbols.applicable_accessibilities = public +dotnet_naming_rule.public_members_must_be_capitalized.style = first_word_upper_case_style +dotnet_naming_style.first_word_upper_case_style.capitalization = first_word_upper +dotnet_naming_rule.public_members_must_be_capitalized.severity = warning -# IDE0003: 한정자 제거 -dotnet_style_qualification_for_property = false:none +# SA0001: XML comment analysis is disabled due to project configuration +dotnet_diagnostic.SA0001.severity = none -# IDE0059: 불필요한 값 할당 -csharp_style_unused_value_assignment_preference = unused_local_variable:suggestion +# SA1101: Prefix local calls with this +dotnet_diagnostic.SA1101.severity = none -# IDE0056: 인덱스 연산자 사용 -csharp_style_prefer_index_operator = true:silent +# SA1309: Field names should not begin with underscore +dotnet_diagnostic.SA1309.severity = none -# IDE0032: auto 속성 사용 -dotnet_style_prefer_auto_properties = true:suggestion +# SA1600: Elements should be documented +dotnet_diagnostic.SA1600.severity = none -# IDE0057: 범위 연산자 사용 -csharp_style_prefer_range_operator = false +# S1133: Deprecated code should be removed +dotnet_diagnostic.S1133.severity = none + +# S1125: Boolean literals should not be redundant +dotnet_diagnostic.S1125.severity = none + +# S2372: Exceptions should not be thrown from property getters +dotnet_diagnostic.S2372.severity = none + +# S3881: "IDisposable" should be implemented correctly +dotnet_diagnostic.S3881.severity = none + +# S3971: "GC.SuppressFinalize" should not be called +dotnet_diagnostic.S3971.severity = none + +# S4143: Collection elements should not be replaced unconditionally +dotnet_diagnostic.S4143.severity = none + +# S6605: Collection-specific "Exists" method should be used instead of the "Any" extension +dotnet_diagnostic.S6605.severity = none + +# S6608: Prefer indexing instead of "Enumerable" methods on types implementing "IList" +dotnet_diagnostic.S6608.severity = none + +# MEN007: Use a single return +dotnet_diagnostic.MEN007.severity = none + +# MEN016: Avoid top-level statements +dotnet_diagnostic.MEN016.severity = none + +[*.csproj] +quote_type = double + +[*.sln] +indent_style = tab +indent_size = 2 + +[hooks/*] +indent_size = 2 +continuation_indent_size = 2 + +[stylecop.json] +max_line_length = diff --git a/.vscode/settings.json b/.vscode/settings.json index b460f96..cad5ced 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,9 @@ { + "[csharp]": { + "editor.rulers": [ + 100 + ] + }, "files.exclude": { ".vs": true, "**/bin": true, diff --git a/Directory.Build.props b/Directory.Build.props index cf60c78..b9dfd85 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,7 +21,31 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> + + + + + all + + runtime; build; native; contentfiles; analyzers + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + + + Menees.Analyzers.Settings.xml + + + net6.0;net7.0;net8.0;netstandard2.1 net8.0 @@ -51,8 +75,10 @@ SOFTWARE. --> communication $(MSBuildThisFileDirectory).build/public.snk + + \ No newline at end of file diff --git a/Menees.Analyzers.Settings.xml b/Menees.Analyzers.Settings.xml new file mode 100644 index 0000000..fdd5b33 --- /dev/null +++ b/Menees.Analyzers.Settings.xml @@ -0,0 +1,5 @@ + + + 100 + 200 + diff --git a/src/JSSoft.Communication.Client/ClientContext.cs b/src/JSSoft.Communication.Client/ClientContext.cs index 4029d11..c4e85ba 100644 --- a/src/JSSoft.Communication.Client/ClientContext.cs +++ b/src/JSSoft.Communication.Client/ClientContext.cs @@ -1,32 +1,15 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System.ComponentModel.Composition; -namespace JSSoft.Communication.ConsoleApp; +namespace JSSoft.Communication.Client; [Export(typeof(IServiceContext))] [method: ImportingConstructor] -sealed class ClientContext([ImportMany] IService[] services) +internal sealed class ClientContext([ImportMany] IService[] services) : Communication.ClientContext(services) { } diff --git a/src/JSSoft.Communication.Client/Program.cs b/src/JSSoft.Communication.Client/Program.cs index 6100944..2405340 100644 --- a/src/JSSoft.Communication.Client/Program.cs +++ b/src/JSSoft.Communication.Client/Program.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using JSSoft.Communication.ConsoleApp; diff --git a/src/JSSoft.Communication.Client/Services/DataService.cs b/src/JSSoft.Communication.Client/Services/DataService.cs index 6376cbc..2342c4b 100644 --- a/src/JSSoft.Communication.Client/Services/DataService.cs +++ b/src/JSSoft.Communication.Client/Services/DataService.cs @@ -1,37 +1,22 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; +using JSSoft.Communication.Services; -namespace JSSoft.Communication.Services; +namespace JSSoft.Communication.Client.Services; [Export(typeof(IService))] [Export(typeof(IDataService))] -sealed class DataService : ClientService, IDataService +internal sealed class DataService : ClientService, IDataService { - public Task CreateDataBaseAsync(string dataBaseName, CancellationToken cancellationToken) + public Task CreateDataBaseAsync( + string dataBaseName, CancellationToken cancellationToken) { return Server.CreateDataBaseAsync(dataBaseName, cancellationToken); } diff --git a/src/JSSoft.Communication.Client/Services/UserService.cs b/src/JSSoft.Communication.Client/Services/UserService.cs index a86dba8..4409480 100644 --- a/src/JSSoft.Communication.Client/Services/UserService.cs +++ b/src/JSSoft.Communication.Client/Services/UserService.cs @@ -1,103 +1,86 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; +using JSSoft.Communication.Services; -namespace JSSoft.Communication.Services; +namespace JSSoft.Communication.Client.Services; [Export(typeof(IService))] [Export(typeof(IUserService))] [Export(typeof(INotifyUserService))] -sealed class UserService : ClientService, IUserService, IUserCallback, INotifyUserService +internal sealed class UserService + : ClientService, IUserService, IUserCallback, INotifyUserService { - public Task CreateAsync(Guid token, string userID, string password, Authority authority, CancellationToken cancellationToken) - => Server.CreateAsync(token, userID, password, authority, cancellationToken); - - public Task DeleteAsync(Guid token, string userID, CancellationToken cancellationToken) - => Server.DeleteAsync(token, userID, cancellationToken); + public event EventHandler? LoggedIn; - public Task RenameAsync(Guid token, string userName, CancellationToken cancellationToken) - => Server.RenameAsync(token, userName, cancellationToken); + public event EventHandler? LoggedOut; - public Task SetAuthorityAsync(Guid token, string userID, Authority authority, CancellationToken cancellationToken) - => Server.SetAuthorityAsync(token, userID, authority, cancellationToken); + public event EventHandler? Created; - public Task LoginAsync(string userID, string password, CancellationToken cancellationToken) - => Server.LoginAsync(userID, password, cancellationToken); + public event EventHandler? Deleted; - public Task LogoutAsync(Guid token, CancellationToken cancellationToken) - => Server.LogoutAsync(token, cancellationToken); + public event EventHandler? MessageReceived; - public Task<(string userName, Authority authority)> GetInfoAsync(Guid token, string userID, CancellationToken cancellationToken) - => Server.GetInfoAsync(token, userID, cancellationToken); + public event EventHandler? Renamed; - public Task GetUsersAsync(Guid token, CancellationToken cancellationToken) - => Server.GetUsersAsync(token, cancellationToken); + public event EventHandler? AuthorityChanged; - public Task IsOnlineAsync(Guid token, string userID, CancellationToken cancellationToken) - => Server.IsOnlineAsync(token, userID, cancellationToken); + public Task CreateAsync(UserCreateOptions options, CancellationToken cancellationToken) + => Server.CreateAsync(options, cancellationToken); - public Task SendMessageAsync(Guid token, string userID, string message, CancellationToken cancellationToken) - => Server.SendMessageAsync(token, userID, message, cancellationToken); + public Task DeleteAsync(UserDeleteOptions options, CancellationToken cancellationToken) + => Server.DeleteAsync(options, cancellationToken); - public event EventHandler? LoggedIn; + public Task RenameAsync(UserRenameOptions options, CancellationToken cancellationToken) + => Server.RenameAsync(options, cancellationToken); - public event EventHandler? LoggedOut; + public Task SetAuthorityAsync(UserAuthorityOptions options, CancellationToken cancellationToken) + => Server.SetAuthorityAsync(options, cancellationToken); - public event EventHandler? Created; + public Task LoginAsync(UserLoginOptions options, CancellationToken cancellationToken) + => Server.LoginAsync(options, cancellationToken); - public event EventHandler? Deleted; + public Task LogoutAsync(Guid token, CancellationToken cancellationToken) + => Server.LogoutAsync(token, cancellationToken); - public event EventHandler? MessageReceived; + public Task GetInfoAsync( + Guid token, string userId, CancellationToken cancellationToken) + => Server.GetInfoAsync(token, userId, cancellationToken); - public event EventHandler? Renamed; + public Task GetUsersAsync(Guid token, CancellationToken cancellationToken) + => Server.GetUsersAsync(token, cancellationToken); - public event EventHandler? AuthorityChanged; + public Task IsOnlineAsync(Guid token, string userId, CancellationToken cancellationToken) + => Server.IsOnlineAsync(token, userId, cancellationToken); - #region IUserCallback + public Task SendMessageAsync( + UserSendMessageOptions options, CancellationToken cancellationToken) + => Server.SendMessageAsync(options, cancellationToken); - void IUserCallback.OnCreated(string userID) - => Created?.Invoke(this, new UserEventArgs(userID)); + void IUserCallback.OnCreated(string userId) + => Created?.Invoke(this, new UserEventArgs(userId)); - void IUserCallback.OnDeleted(string userID) - => Deleted?.Invoke(this, new UserEventArgs(userID)); + void IUserCallback.OnDeleted(string userId) + => Deleted?.Invoke(this, new UserEventArgs(userId)); - void IUserCallback.OnLoggedIn(string userID) - => LoggedIn?.Invoke(this, new UserEventArgs(userID)); + void IUserCallback.OnLoggedIn(string userId) + => LoggedIn?.Invoke(this, new UserEventArgs(userId)); - void IUserCallback.OnLoggedOut(string userID) - => LoggedOut?.Invoke(this, new UserEventArgs(userID)); + void IUserCallback.OnLoggedOut(string userId) + => LoggedOut?.Invoke(this, new UserEventArgs(userId)); void IUserCallback.OnMessageReceived(string sender, string receiver, string message) => MessageReceived?.Invoke(this, new UserMessageEventArgs(sender, receiver, message)); - void IUserCallback.OnRenamed(string userID, string userName) - => Renamed?.Invoke(this, new UserNameEventArgs(userID, userName)); - - void IUserCallback.OnAuthorityChanged(string userID, Authority authority) - => AuthorityChanged?.Invoke(this, new UserAuthorityEventArgs(userID, authority)); + void IUserCallback.OnRenamed(string userId, string userName) + => Renamed?.Invoke(this, new UserNameEventArgs(userId, userName)); - #endregion + void IUserCallback.OnAuthorityChanged(string userId, Authority authority) + => AuthorityChanged?.Invoke(this, new UserAuthorityEventArgs(userId, authority)); } diff --git a/src/JSSoft.Communication.Server/Program.cs b/src/JSSoft.Communication.Server/Program.cs index 6100944..2405340 100644 --- a/src/JSSoft.Communication.Server/Program.cs +++ b/src/JSSoft.Communication.Server/Program.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using JSSoft.Communication.ConsoleApp; diff --git a/src/JSSoft.Communication.Server/ServerContext.cs b/src/JSSoft.Communication.Server/ServerContext.cs index efe490f..514dcfa 100644 --- a/src/JSSoft.Communication.Server/ServerContext.cs +++ b/src/JSSoft.Communication.Server/ServerContext.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System.ComponentModel.Composition; @@ -26,7 +9,7 @@ namespace JSSoft.Communication.ConsoleApp; [Export(typeof(IServiceContext))] [method: ImportingConstructor] -sealed class ServerContext([ImportMany] IService[] services) +internal sealed class ServerContext([ImportMany] IService[] services) : Communication.ServerContext(services) { } diff --git a/src/JSSoft.Communication.Server/Services/DataService.cs b/src/JSSoft.Communication.Server/Services/DataService.cs index ee68929..37f6465 100644 --- a/src/JSSoft.Communication.Server/Services/DataService.cs +++ b/src/JSSoft.Communication.Server/Services/DataService.cs @@ -1,37 +1,21 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; +using JSSoft.Communication.Services; using JSSoft.Communication.Threading; -namespace JSSoft.Communication.Services; +namespace JSSoft.Communication.Server.Services; [Export(typeof(IService))] [Export(typeof(IDataService))] -sealed class DataService : ServerService, IDataService, IDisposable +internal sealed class DataService : ServerService, IDataService, IDisposable { private readonly HashSet _dataBases = []; private Dispatcher? _dispatcher; @@ -41,26 +25,31 @@ public DataService() _dispatcher = new Dispatcher(this); } - public Task CreateDataBaseAsync(string dataBaseName, CancellationToken cancellationToken) + public Dispatcher Dispatcher => _dispatcher ?? throw new ObjectDisposedException($"{this}"); + + public Task CreateDataBaseAsync( + string dataBaseName, CancellationToken cancellationToken) { - return Dispatcher.InvokeAsync(() => + return Dispatcher.InvokeAsync(() => CreateDataBase(dataBaseName)); + + DateTime CreateDataBase(string dataBaseName) { if (_dataBases.Contains(dataBaseName) == true) - throw new ArgumentNullException(nameof(dataBaseName)); + { + throw new ArgumentException("The database already exists.", nameof(dataBaseName)); + } + _dataBases.Add(dataBaseName); return DateTime.UtcNow; - }); + } } public void Dispose() { - if (_dispatcher == null) - throw new ObjectDisposedException($"{this}"); + ObjectDisposedException.ThrowIf(_dispatcher == null, this); _dispatcher.Dispose(); _dispatcher = null; GC.SuppressFinalize(this); } - - public Dispatcher Dispatcher => _dispatcher ?? throw new ObjectDisposedException($"{this}"); -} \ No newline at end of file +} diff --git a/src/JSSoft.Communication.Server/Services/User.cs b/src/JSSoft.Communication.Server/Services/User.cs new file mode 100644 index 0000000..7fcbb62 --- /dev/null +++ b/src/JSSoft.Communication.Server/Services/User.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; +using JSSoft.Communication.Services; + +namespace JSSoft.Communication.Server.Services; + +internal sealed class User +{ + public string UserId { get; set; } = string.Empty; + + public string UserName { get; set; } = string.Empty; + + public string Password { get; set; } = string.Empty; + + public Authority Authority { get; set; } + + public Guid Token { get; set; } +} diff --git a/src/JSSoft.Communication.Server/Services/UserInfo.cs b/src/JSSoft.Communication.Server/Services/UserInfo.cs deleted file mode 100644 index f71c857..0000000 --- a/src/JSSoft.Communication.Server/Services/UserInfo.cs +++ /dev/null @@ -1,38 +0,0 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using System; - -namespace JSSoft.Communication.Services; - -class UserInfo -{ - public string UserID { get; set; } = string.Empty; - - public string UserName { get; set; } = string.Empty; - - public string Password { get; set; } = string.Empty; - - public Authority Authority { get; set; } - - public Guid Token { get; set; } -} \ No newline at end of file diff --git a/src/JSSoft.Communication.Server/Services/UserService.cs b/src/JSSoft.Communication.Server/Services/UserService.cs index af765b4..bb5bf6f 100644 --- a/src/JSSoft.Communication.Server/Services/UserService.cs +++ b/src/JSSoft.Communication.Server/Services/UserService.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections.Generic; @@ -26,25 +9,27 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using JSSoft.Communication.Services; using JSSoft.Communication.Threading; -namespace JSSoft.Communication.Services; +namespace JSSoft.Communication.Server.Services; [Export(typeof(IService))] [Export(typeof(IUserService))] [Export(typeof(INotifyUserService))] -sealed class UserService : ServerService, IUserService, INotifyUserService, IDisposable +internal sealed class UserService + : ServerService, IUserService, INotifyUserService, IDisposable { - private readonly Dictionary _userByID = []; - private readonly Dictionary _userByToken = []; + private readonly Dictionary _userById = []; + private readonly Dictionary _userByToken = []; private Dispatcher? _dispatcher; public UserService() { - _userByID.Add("admin", new UserInfo() + _userById.Add("admin", new User() { - UserID = "admin", + UserId = "admin", Password = "admin", UserName = "Administrator", Authority = Authority.Admin, @@ -52,59 +37,82 @@ public UserService() for (var i = 0; i < 10; i++) { - var user = new UserInfo() + var user = new User() { - UserID = $"user{i}", + UserId = $"user{i}", Password = "1234", UserName = $"사용자{i}", Authority = Authority.Member, }; - _userByID.Add(user.UserID, user); + _userById.Add(user.UserId, user); } + _dispatcher = new Dispatcher(this); } - public Task CreateAsync(Guid token, string userID, string password, Authority authority, CancellationToken cancellationToken) + public event EventHandler? Created; + + public event EventHandler? Deleted; + + public event EventHandler? LoggedIn; + + public event EventHandler? LoggedOut; + + public event EventHandler? MessageReceived; + + public event EventHandler? Renamed; + + public event EventHandler? AuthorityChanged; + + public Dispatcher Dispatcher => _dispatcher + ?? throw new InvalidOperationException($"'{this}' has not been initialized."); + + public Task CreateAsync(UserCreateOptions options, CancellationToken cancellationToken) { return Dispatcher.InvokeAsync(() => { - ValidateCreate(token, userID, password); + ValidateCreate(options); - var user = _userByToken[token]; - var userInfo = new UserInfo() + var user = new User() { - UserID = userID, - Password = password, + UserId = options.UserId, + Password = options.Password, UserName = string.Empty, - Authority = authority + Authority = options.Authority, }; - _userByID.Add(userID, userInfo); - Client.OnCreated(userID); - Created?.Invoke(this, new UserEventArgs(userID)); + _userById.Add(options.UserId, user); + Client.OnCreated(options.UserId); + Created?.Invoke(this, new UserEventArgs(options.UserId)); }); } - public Task DeleteAsync(Guid token, string userID, CancellationToken cancellationToken) + public Task DeleteAsync(UserDeleteOptions options, CancellationToken cancellationToken) { return Dispatcher.InvokeAsync(() => { - ValidateDelete(token, userID); + ValidateDelete(options); - _userByID.Remove(userID); - Client.OnDeleted(userID); - Deleted?.Invoke(this, new UserEventArgs(userID)); + var userId = options.UserId; + _userById.Remove(userId); + Client.OnDeleted(userId); + Deleted?.Invoke(this, new UserEventArgs(userId)); }); } - public Task<(string, Authority)> GetInfoAsync(Guid token, string userID, CancellationToken cancellationToken) + public Task GetInfoAsync( + Guid token, string userId, CancellationToken cancellationToken) { return Dispatcher.InvokeAsync(() => { ValidateUser(token); - ValidateUser(userID); + ValidateUser(userId); - var user = _userByID[userID]; - return (user.UserName, user.Authority); + var user = _userById[userId]; + return new UserInfo + { + UserName = user.UserName, + Authority = user.Authority, + }; }); } @@ -114,34 +122,36 @@ public Task GetUsersAsync(Guid token, CancellationToken cancellationTo { ValidateUser(token); - return _userByID.Keys.ToArray(); + return _userById.Keys.ToArray(); }); } - public Task IsOnlineAsync(Guid token, string userID, CancellationToken cancellationToken) + public Task IsOnlineAsync(Guid token, string userId, CancellationToken cancellationToken) { return Dispatcher.InvokeAsync(() => { ValidateUser(token); - ValidateUser(userID); + ValidateUser(userId); - var user = _userByID[userID]; + var user = _userById[userId]; return user.Token != Guid.Empty; }); } - public Task LoginAsync(string userID, string password, CancellationToken cancellationToken) + public Task LoginAsync(UserLoginOptions options, CancellationToken cancellationToken) { return Dispatcher.InvokeAsync(() => { - ValidatePassword(userID, password); + var userId = options.UserId; + var password = options.Password; + ValidatePassword(userId, password); var token = Guid.NewGuid(); - var user = _userByID[userID]; + var user = _userById[userId]; user.Token = token; _userByToken.Add(token, user); - Client.OnLoggedIn(userID); - LoggedIn?.Invoke(this, new UserEventArgs(userID)); + Client.OnLoggedIn(userId); + LoggedIn?.Invoke(this, new UserEventArgs(userId)); return token; }); } @@ -155,158 +165,174 @@ public Task LogoutAsync(Guid token, CancellationToken cancellationToken) var user = _userByToken[token]; user.Token = Guid.Empty; _userByToken.Remove(token); - Client.OnLoggedOut(user.UserID); - LoggedOut?.Invoke(this, new UserEventArgs(user.UserID)); + Client.OnLoggedOut(user.UserId); + LoggedOut?.Invoke(this, new UserEventArgs(user.UserId)); }); } - public Task SendMessageAsync(Guid token, string userID, string message, CancellationToken cancellationToken) + public Task SendMessageAsync( + UserSendMessageOptions options, CancellationToken cancellationToken) { return Dispatcher.InvokeAsync(() => { + var token = options.Token; + var userId = options.UserId; + var message = options.Message; ValidateUser(token); - ValidateOnline(userID); + ValidateOnline(userId); ValidateMessage(message); var user = _userByToken[token]; - Client.OnMessageReceived(user.UserID, userID, message); - MessageReceived?.Invoke(this, new UserMessageEventArgs(user.UserID, userID, message)); + Client.OnMessageReceived(user.UserId, userId, message); + MessageReceived?.Invoke(this, new UserMessageEventArgs(user.UserId, userId, message)); }); } - public Task RenameAsync(Guid token, string userName, CancellationToken cancellationToken) + public Task RenameAsync(UserRenameOptions options, CancellationToken cancellationToken) { return Dispatcher.InvokeAsync(() => { + var token = options.Token; + var userName = options.UserName; ValidateRename(token, userName); var user = _userByToken[token]; user.UserName = userName; - Client.OnRenamed(user.UserID, userName); - Renamed?.Invoke(this, new UserNameEventArgs(user.UserID, userName)); + Client.OnRenamed(user.UserId, userName); + Renamed?.Invoke(this, new UserNameEventArgs(user.UserId, userName)); }); } - public Task SetAuthorityAsync(Guid token, string userID, Authority authority, CancellationToken cancellationToken) + public Task SetAuthorityAsync(UserAuthorityOptions options, CancellationToken cancellationToken) { return Dispatcher.InvokeAsync(() => { - ValidateSetAuthority(token, userID); + var token = options.Token; + var userId = options.UserId; + var authority = options.Authority; + ValidateSetAuthority(token, userId); - var user = _userByID[userID]; + var user = _userById[userId]; user.Authority = authority; - Client.OnAuthorityChanged(userID, authority); - AuthorityChanged?.Invoke(this, new UserAuthorityEventArgs(userID, authority)); + Client.OnAuthorityChanged(userId, authority); + AuthorityChanged?.Invoke(this, new UserAuthorityEventArgs(userId, authority)); }); } public void Dispose() { - if (_dispatcher == null) - throw new ObjectDisposedException($"{this}"); + ObjectDisposedException.ThrowIf(_dispatcher == null, $"{this}"); _dispatcher.Dispose(); _dispatcher = null; GC.SuppressFinalize(this); } - public Dispatcher Dispatcher + private void ValidateUser(string userId) { - get + Dispatcher.VerifyAccess(); + if (userId == null) { - if (_dispatcher == null) - throw new InvalidOperationException($"'{this}' has not been initialized."); - return _dispatcher; + throw new ArgumentNullException(nameof(userId)); } - } - - public event EventHandler? Created; - - public event EventHandler? Deleted; - - public event EventHandler? LoggedIn; - public event EventHandler? LoggedOut; - - public event EventHandler? MessageReceived; - - public event EventHandler? Renamed; - - public event EventHandler? AuthorityChanged; - - private void ValidateUser(string userID) - { - Dispatcher.VerifyAccess(); - if (userID == null) - throw new ArgumentNullException(nameof(userID)); - if (_userByID.ContainsKey(userID) != true) - throw new ArgumentException("Invalid userID", nameof(userID)); + if (_userById.ContainsKey(userId) != true) + { + throw new ArgumentException("Invalid userID", nameof(userId)); + } } - private void ValidateNotUser(string userID) + private void ValidateUser(Guid token) { Dispatcher.VerifyAccess(); - if (userID == null) - throw new ArgumentNullException(nameof(userID)); - if (_userByID.ContainsKey(userID) == true) - throw new ArgumentException("user is already exists.", nameof(userID)); + if (_userByToken.ContainsKey(token) != true) + { + throw new ArgumentException("Invalid token.", nameof(token)); + } } - private void ValidateUser(Guid token) + private void ValidateNotUser(string userId) { Dispatcher.VerifyAccess(); - if (_userByToken.ContainsKey(token) != true) - throw new ArgumentException("Invalid token.", nameof(token)); + if (userId == null) + { + throw new ArgumentNullException(nameof(userId)); + } + + if (_userById.ContainsKey(userId) == true) + { + throw new ArgumentException("user is already exists.", nameof(userId)); + } } private void ValidatePassword(string password) { Dispatcher.VerifyAccess(); if (password == null) + { throw new ArgumentNullException(nameof(password)); + } + if (password == string.Empty) + { throw new ArgumentException("Invalid password.", nameof(password)); + } + if (password.Length < 4) - throw new ArgumentException("length of password must be greater or equal than 4.", nameof(password)); + { + throw new ArgumentException( + "length of password must be greater or equal than 4.", nameof(password)); + } } - private void ValidatePassword(string userID, string password) + private void ValidatePassword(string userId, string password) { Dispatcher.VerifyAccess(); - ValidateUser(userID); + ValidateUser(userId); if (password == null) + { throw new ArgumentNullException(nameof(password)); - var user = _userByID[userID]; + } + + var user = _userById[userId]; if (user.Password != password) + { throw new InvalidOperationException("wrong userID or password."); + } } - private void ValidateCreate(Guid token, string userID, string password) + private void ValidateCreate(UserCreateOptions options) { Dispatcher.VerifyAccess(); - ValidateUser(token); - ValidateNotUser(userID); - ValidatePassword(password); - var user = _userByToken[token]; + ValidateUser(options.Token); + ValidateNotUser(options.UserId); + ValidatePassword(options.Password); + var user = _userByToken[options.Token]; if (user.Authority != Authority.Admin) + { throw new InvalidOperationException("permission denied."); + } } private void ValidateMessage(string message) { Dispatcher.VerifyAccess(); if (message == null) + { throw new ArgumentNullException(nameof(message)); + } } - private void ValidateOnline(string userID) + private void ValidateOnline(string userId) { Dispatcher.VerifyAccess(); - ValidateUser(userID); - var user = _userByID[userID]; + ValidateUser(userId); + var user = _userById[userId]; if (user.Token == Guid.Empty) + { throw new InvalidOperationException($"user is offline."); + } } private void ValidateRename(Guid token, string userName) @@ -314,45 +340,75 @@ private void ValidateRename(Guid token, string userName) Dispatcher.VerifyAccess(); ValidateUser(token); if (userName == null) + { throw new ArgumentNullException(nameof(userName)); + } + if (userName == string.Empty) + { throw new ArgumentException("Invalid name.", nameof(userName)); + } + var user = _userByToken[token]; if (user.UserName == userName) + { throw new ArgumentException("same name can not set.", nameof(userName)); - if (user.UserID == "admin") + } + + if (user.UserId == "admin") + { throw new InvalidOperationException("permission denied."); + } } - private void ValidateSetAuthority(Guid token, string userID) + private void ValidateSetAuthority(Guid token, string userId) { Dispatcher.VerifyAccess(); ValidateUser(token); - ValidateUser(userID); + ValidateUser(userId); var user1 = _userByToken[token]; - if (user1.UserID == userID) + if (user1.UserId == userId) + { throw new InvalidOperationException("can not set authority."); + } + if (user1.Authority != Authority.Admin) + { throw new InvalidOperationException("permission denied."); - var user2 = _userByID[userID]; + } + + var user2 = _userById[userId]; if (user2.Token != Guid.Empty) + { throw new InvalidOperationException("can not set authority of online user."); - if (userID == "admin") + } + + if (userId == "admin") + { throw new InvalidOperationException("permission denied."); + } } - private void ValidateDelete(Guid token, string userID) + private void ValidateDelete(UserDeleteOptions options) { Dispatcher.VerifyAccess(); - ValidateUser(token); - ValidateNotUser(userID); - var user1 = _userByToken[token]; + ValidateUser(options.Token); + ValidateNotUser(options.UserId); + var user1 = _userByToken[options.Token]; if (user1.Authority != Authority.Admin) + { throw new InvalidOperationException("permission denied."); - var user2 = _userByID[userID]; + } + + var user2 = _userById[options.UserId]; if (user2.Token != Guid.Empty) + { throw new InvalidOperationException("can not delete online user."); - if (userID == "admin") + } + + if (options.UserId == "admin") + { throw new InvalidOperationException("permission denied."); + } } } diff --git a/src/JSSoft.Communication/AdaptorProvider.cs b/src/JSSoft.Communication/AdaptorProvider.cs index 937c377..799dc1b 100644 --- a/src/JSSoft.Communication/AdaptorProvider.cs +++ b/src/JSSoft.Communication/AdaptorProvider.cs @@ -1,48 +1,31 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; namespace JSSoft.Communication; -sealed class AdaptorProvider : IAdaptorProvider +internal sealed class AdaptorProvider : IAdaptorProvider { public const string DefaultName = "grpc"; + public static readonly AdaptorProvider Default = new(); - public IAdaptor Create(IServiceContext serviceContext, IInstanceContext instanceContext, ServiceToken token) - { - // Environment.SetEnvironmentVariable("GRPC_VERBOSITY", "DEBUG"); - // Environment.SetEnvironmentVariable("GRPC_TRACE", "all"); - // global::Grpc.Core.GrpcEnvironment.SetLogger(new global::Grpc.Core.Logging.ConsoleLogger()); + public string Name => DefaultName; + public IAdaptor Create( + IServiceContext serviceContext, IInstanceContext instanceContext, ServiceToken serviceToken) + { if (serviceContext is ServerContext serverContext) + { return new Grpc.AdaptorServer(serverContext, instanceContext); + } else if (serviceContext is ClientContext clientContext) + { return new Grpc.AdaptorClient(clientContext, instanceContext); + } throw new NotSupportedException($"'{serviceContext.GetType()}' is not supported."); } - - public string Name => DefaultName; - - public static readonly AdaptorProvider Default = new(); } diff --git a/src/JSSoft.Communication/ClientContext.cs b/src/JSSoft.Communication/ClientContext.cs index 1bd9cda..9e2b9f8 100644 --- a/src/JSSoft.Communication/ClientContext.cs +++ b/src/JSSoft.Communication/ClientContext.cs @@ -1,26 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using System; +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication; diff --git a/src/JSSoft.Communication/ClientService.cs b/src/JSSoft.Communication/ClientService.cs index afa0f52..4368318 100644 --- a/src/JSSoft.Communication/ClientService.cs +++ b/src/JSSoft.Communication/ClientService.cs @@ -1,27 +1,12 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +// File may only contain a single type +#pragma warning disable SA1402 using System; -using System.Reflection; namespace JSSoft.Communication; @@ -41,18 +26,20 @@ public ClientService(TClient client) public ClientService() : base(typeof(TServer), typeof(TClient)) { - if (this is TClient client) + var obj = this; + if (obj is TClient client) { _client = client; - } else { - throw new InvalidOperationException($"'{GetType()}' must be implemented by '{typeof(TClient)}'."); + throw new InvalidOperationException( + $"'{GetType()}' must be implemented by '{typeof(TClient)}'."); } } - public TServer Server => _server ?? throw new InvalidOperationException("Server is not created."); + public TServer Server + => _server ?? throw new InvalidOperationException("Server is not created."); protected virtual TClient CreateClient(IPeer peer) => _client; @@ -85,7 +72,8 @@ public ClientService() { } - public TServer Server => _server ?? throw new InvalidOperationException("Server is not created."); + public TServer Server + => _server ?? throw new InvalidOperationException("Server is not created."); protected virtual void OnServiceCreated(IPeer peer, TServer server) { diff --git a/src/JSSoft.Communication/EndPointUtility.cs b/src/JSSoft.Communication/EndPointUtility.cs index 3870098..6d5afe7 100644 --- a/src/JSSoft.Communication/EndPointUtility.cs +++ b/src/JSSoft.Communication/EndPointUtility.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Diagnostics.CodeAnalysis; @@ -30,7 +13,7 @@ namespace JSSoft.Communication; public static class EndPointUtility { - public static (string host, int port) GetElements(EndPoint endPoint) + public static (string Host, int Port) GetElements(EndPoint endPoint) { if (endPoint is DnsEndPoint dnsEndPoint) { @@ -40,6 +23,7 @@ public static (string host, int port) GetElements(EndPoint endPoint) { return ($"{iPEndPoint.Address}", iPEndPoint.Port); } + throw new NotSupportedException($"'{endPoint}' is not supported."); } @@ -53,6 +37,7 @@ public static string ToString(EndPoint endPoint) { return $"{iPEndPoint.Address}:{iPEndPoint.Port}"; } + throw new NotSupportedException($"'{endPoint}' is not supported."); } @@ -86,7 +71,8 @@ public static bool TryParse(string text, [MaybeNullWhen(false)] out EndPoint end } #if NETSTANDARD - internal static global::Grpc.Core.ServerPort GetServerPort(EndPoint endPoint, global::Grpc.Core.ServerCredentials credentials) + internal static global::Grpc.Core.ServerPort GetServerPort( + EndPoint endPoint, global::Grpc.Core.ServerCredentials credentials) { if (endPoint is DnsEndPoint dnsEndPoint) { diff --git a/src/JSSoft.Communication/Extensions/ISerializerExtensions.cs b/src/JSSoft.Communication/Extensions/ISerializerExtensions.cs index dba7f59..fe9fb91 100644 --- a/src/JSSoft.Communication/Extensions/ISerializerExtensions.cs +++ b/src/JSSoft.Communication/Extensions/ISerializerExtensions.cs @@ -1,31 +1,14 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Threading; namespace JSSoft.Communication.Extensions; -static class ISerializerExtensions +internal static class ISerializerExtensions { public static string[] SerializeMany(this ISerializer @this, Type[] types, object?[] args) { @@ -36,6 +19,7 @@ public static string[] SerializeMany(this ISerializer @this, Type[] types, objec var value = args[i]; items[i] = @this.Serialize(type, value); } + return items; } @@ -48,10 +32,12 @@ public static string[] SerializeMany(this ISerializer @this, Type[] types, objec var value = datas[i]; items[i] = @this.Deserialize(type, value); } + return items; } - public static object?[] DeserializeMany(this ISerializer @this, Type[] types, string[] datas, CancellationToken? cancellationToken) + public static object?[] DeserializeMany( + this ISerializer @this, Type[] types, string[] datas, CancellationToken? cancellationToken) { var length = cancellationToken != null ? datas.Length + 1 : datas.Length; var items = new object?[length]; @@ -61,10 +47,12 @@ public static string[] SerializeMany(this ISerializer @this, Type[] types, objec var value = datas[i]; items[i] = @this.Deserialize(type, value); } + if (cancellationToken != null) { items[datas.Length] = cancellationToken; } + return items; } } diff --git a/src/JSSoft.Communication/Extensions/IServiceContextExtensions.cs b/src/JSSoft.Communication/Extensions/IServiceContextExtensions.cs index 5831031..0fe215d 100644 --- a/src/JSSoft.Communication/Extensions/IServiceContextExtensions.cs +++ b/src/JSSoft.Communication/Extensions/IServiceContextExtensions.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Threading.Tasks; @@ -38,11 +21,11 @@ public static void Error(this IServiceContext @this, string message) /// Close the service context without exception throwing. /// /// Indicates a service context instance to close. - /// Indicates a token to close. you can get the token when + /// Indicates a token to close. you can get the token when /// the service context is opened. /// /// - /// If the service context is closed successfully, it returns 0. + /// If the service context is closed successfully, it returns 0. /// If the service context is faulted, it returns 1. /// public static async Task ReleaseAsync(this IServiceContext @this, Guid token) @@ -55,6 +38,7 @@ public static async Task ReleaseAsync(this IServiceContext @this, Guid toke } catch { + // do nothing } } diff --git a/src/JSSoft.Communication/Grpc/AdaptorClient.cs b/src/JSSoft.Communication/Grpc/AdaptorClient.cs index 9b4e0ac..6cf7899 100644 --- a/src/JSSoft.Communication/Grpc/AdaptorClient.cs +++ b/src/JSSoft.Communication/Grpc/AdaptorClient.cs @@ -1,34 +1,19 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using Grpc.Core; -using JSSoft.Communication.Extensions; -using JSSoft.Communication.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using Grpc.Core; +using JSSoft.Communication.Extensions; +using JSSoft.Communication.Logging; +using Newtonsoft.Json; + #if NETSTANDARD using GrpcChannel = Grpc.Core.Channel; #elif NET @@ -38,7 +23,7 @@ namespace JSSoft.Communication.Grpc; -sealed class AdaptorClient : IAdaptor +internal sealed class AdaptorClient : IAdaptor { private static readonly TimeSpan Timeout = new(0, 0, 15); @@ -59,23 +44,32 @@ public AdaptorClient(IServiceContext serviceContext, IInstanceContext instanceCo _serviceContext = serviceContext; _instanceContext = instanceContext; _serviceByName = serviceContext.Services; - _methodsByService = _serviceByName.ToDictionary(item => item.Value, item => new MethodDescriptorCollection(item.Value.ClientType)); + _methodsByService = _serviceByName.ToDictionary( + keySelector: item => item.Value, + elementSelector: item => new MethodDescriptorCollection(item.Value.ClientType)); } + public event EventHandler? Disconnected; + public async Task OpenAsync(EndPoint endPoint, CancellationToken cancellationToken) { if (_adaptorImpl != null) + { throw new InvalidOperationException("Already opened."); + } + try { #if NETSTANDARD _channel = new Channel(EndPointUtility.ToString(endPoint), ChannelCredentials.Insecure); await _channel.ConnectAsync(deadline: DateTime.UtcNow.AddSeconds(15)); #elif NET - _channel = GrpcChannel.ForAddress($"http://{EndPointUtility.ConvertToIPEndPoint(endPoint)}"); + _channel = GrpcChannel.ForAddress( + $"http://{EndPointUtility.ConvertToIPEndPoint(endPoint)}"); await _channel.ConnectAsync(cancellationToken); #endif - _adaptorImpl = new AdaptorClientImpl(_channel, _serviceContext.Id, _serviceByName.Values.ToArray()); + _adaptorImpl = new AdaptorClientImpl( + _channel, _serviceContext.Id, _serviceByName.Values.ToArray()); await _adaptorImpl.OpenAsync(cancellationToken); _descriptor = _instanceContext.CreateInstance(_adaptorImpl); _cancellationTokenSource = new CancellationTokenSource(); @@ -90,35 +84,43 @@ public async Task OpenAsync(EndPoint endPoint, CancellationToken cancellationTok await _channel.ShutdownAsync(); _channel = null; } + throw; } } public async Task CloseAsync(CancellationToken cancellationToken) { - _cancellationTokenSource?.Cancel(); + if (_cancellationTokenSource is not null) + { + await _cancellationTokenSource.CancelAsync(); + _cancellationTokenSource.Dispose(); + _cancellationTokenSource = null; + } + if (_timer != null) { await _timer.DisposeAsync(); _timer = null; } + if (_adaptorImpl != null) { _instanceContext.DestroyInstance(_adaptorImpl); await _adaptorImpl.CloseAsync(cancellationToken); _adaptorImpl = null; } + if (_task != null) { await _task; } + if (_channel != null) { await _channel.ShutdownAsync(); _channel = null; } - _cancellationTokenSource?.Dispose(); - _cancellationTokenSource = null; } public async ValueTask DisposeAsync() @@ -129,133 +131,34 @@ public async ValueTask DisposeAsync() await _timer.DisposeAsync(); _timer = null; } + if (_adaptorImpl != null) { _instanceContext.DestroyInstance(_adaptorImpl); await _adaptorImpl.TryCloseAsync(cancellationToken: default); _adaptorImpl = null; } + if (_channel != null) { await _channel.ShutdownAsync(); _channel = null; } - GC.SuppressFinalize(this); - } - - public event EventHandler? Disconnected; - private async void Timer_TimerCallback(object? state) - { - try - { - if (_adaptorImpl != null) - { - var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; - var request = new PingRequest(); - await _adaptorImpl.PingAsync(request, metaData); - } - } - catch - { - } + GC.SuppressFinalize(this); } - private async Task PollAsync(CancellationToken cancellationToken) + void IAdaptor.Invoke(InvokeOptions options) { if (_adaptorImpl == null) - throw new InvalidOperationException("adaptor is not set."); - - var closeCode = int.MinValue; - try - { - var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; - using var call = _adaptorImpl.Poll(metaData); - var request = new PollRequest(); - await call.RequestStream.WriteAsync(request); - while (await MoveAsync(call, cancellationToken) == true) - { - var reply = call.ResponseStream.Current; - if (reply.Code != int.MinValue) - { - closeCode = reply.Code; - break; - } - InvokeCallback(reply); - await call.RequestStream.WriteAsync(request); - } - await call.RequestStream.CompleteAsync(); - } - catch (Exception e) - { - closeCode = -2; - LogUtility.Error(e); - } - if (closeCode != int.MinValue) - { - Disconnected?.Invoke(this, EventArgs.Empty); - } - _serviceContext.Debug("Poll finished."); - - static async Task MoveAsync(AsyncDuplexStreamingCall call, CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested == true) - return false; - using var cancellationTokenSource = new CancellationTokenSource(Timeout); - return await call.ResponseStream.MoveNext(cancellationTokenSource.Token); - } - } - - private void InvokeCallback(IService service, string name, string[] data) - { - if (_adaptorImpl == null) throw new InvalidOperationException("adaptor is not set."); - var methodDescriptors = _methodsByService[service]; - if (methodDescriptors.Contains(name) != true) - throw new InvalidOperationException("Invalid method name."); - - var methodDescriptor = methodDescriptors[name]; - var args = _serializer!.DeserializeMany(methodDescriptor.ParameterTypes, data); - var instance = _descriptor!.ClientInstances[service]; - Task.Run(() => methodDescriptor.InvokeAsync(_serviceContext, instance, args)); - } - - private void InvokeCallback(PollReply reply) - { - foreach (var item in reply.Items) - { - var service = _serviceByName[item.ServiceName]; - InvokeCallback(service, item.Name, [.. item.Data]); } - reply.Items.Clear(); - } - - private void HandleReply(InvokeReply reply) - { - if (reply.ID != string.Empty && Type.GetType(reply.ID) is { } exceptionType) - { - ThrowException(exceptionType, reply.Data); - } - } - - private void ThrowException(Type exceptionType, string data) - { - if (_serializer == null) - throw new InvalidOperationException("serializer is not set."); - - if (Newtonsoft.Json.JsonConvert.DeserializeObject(data, exceptionType) is Exception exception) - throw exception; - - throw new UnreachableException($"This code should not be reached in {nameof(ThrowException)}."); - } - - #region IAdaptor - - void IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] args) - { - if (_adaptorImpl == null) - throw new InvalidOperationException("adaptor is not set."); + var instance = options.Instance; + var name = options.Name; + var types = options.Types; + var args = options.Args; var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; var data = _serializer!.SerializeMany(types, args); var request = new InvokeRequest @@ -268,11 +171,17 @@ void IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] HandleReply(reply); } - async void IAdaptor.InvokeOneWay(InstanceBase instance, string name, Type[] types, object?[] args) + async void IAdaptor.InvokeOneWay(InvokeOptions options) { if (_adaptorImpl == null) + { throw new InvalidOperationException("adaptor is not set."); + } + var instance = options.Instance; + var name = options.Name; + var types = options.Types; + var args = options.Args; var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; var data = _serializer!.SerializeMany(types, args); var request = new InvokeRequest @@ -287,16 +196,26 @@ async void IAdaptor.InvokeOneWay(InstanceBase instance, string name, Type[] type } catch { + // do nothing } } - T IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] args) + T IAdaptor.Invoke(InvokeOptions options) { if (_adaptorImpl == null) + { throw new InvalidOperationException("adaptor is not set."); + } + if (_serializer == null) + { throw new InvalidOperationException("serializer is not set."); + } + var instance = options.Instance; + var name = options.Name; + var types = options.Types; + var args = options.Args; var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; var data = _serializer.SerializeMany(types, args); var request = new InvokeRequest @@ -308,18 +227,30 @@ T IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] var reply = _adaptorImpl.Invoke(request, metaData); HandleReply(reply); if (_serializer.Deserialize(typeof(T), reply.Data) is T value) + { return value; + } - throw new UnreachableException($"This code should not be reached in {nameof(IAdaptor.Invoke)}."); + throw new UnreachableException( + $"This code should not be reached in {nameof(IAdaptor.Invoke)}."); } - async Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken) + async Task IAdaptor.InvokeAsync(InvokeOptions options, CancellationToken cancellationToken) { if (_adaptorImpl == null) + { throw new InvalidOperationException("adaptor is not set."); + } + if (_serializer == null) + { throw new InvalidOperationException("serializer is not set."); + } + var instance = options.Instance; + var name = options.Name; + var types = options.Types; + var args = options.Args; var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; var data = _serializer.SerializeMany(types, args); var request = new InvokeRequest @@ -330,24 +261,40 @@ async Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types }; try { - var reply = await _adaptorImpl.InvokeAsync(request, metaData, cancellationToken: cancellationToken); + var reply = await _adaptorImpl.InvokeAsync( + request: request, + headers: metaData, + cancellationToken: cancellationToken); HandleReply(reply); } catch (RpcException e) { if (e.StatusCode == StatusCode.Cancelled) + { cancellationToken.ThrowIfCancellationRequested(); + } + throw; } } - async Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken) + async Task IAdaptor.InvokeAsync( + InvokeOptions options, CancellationToken cancellationToken) { if (_adaptorImpl == null) + { throw new InvalidOperationException("adaptor is not set."); + } + if (_serializer == null) + { throw new InvalidOperationException("serializer is not set."); + } + var instance = options.Instance; + var name = options.Name; + var types = options.Types; + var args = options.Args; var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; var data = _serializer.SerializeMany(types, args); var request = new InvokeRequest @@ -358,17 +305,147 @@ async Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] request.Data.AddRange(data); try { - var reply = await _adaptorImpl.InvokeAsync(request, metaData, cancellationToken: cancellationToken); + var reply = await _adaptorImpl.InvokeAsync( + request: request, + headers: metaData, + cancellationToken: cancellationToken); HandleReply(reply); return _serializer.Deserialize(typeof(T), reply.Data) is T value ? value : default!; } catch (RpcException e) { if (e.StatusCode == StatusCode.Cancelled) + { cancellationToken.ThrowIfCancellationRequested(); + } + throw; } } - #endregion + private static async Task MoveAsync( + AsyncDuplexStreamingCall call, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested == true) + { + return false; + } + + using var cancellationTokenSource = new CancellationTokenSource(Timeout); + return await call.ResponseStream.MoveNext(cancellationTokenSource.Token); + } + + private async void Timer_TimerCallback(object? state) + { + try + { + if (_adaptorImpl != null) + { + var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; + var request = new PingRequest(); + await _adaptorImpl.PingAsync(request, metaData); + } + } + catch + { + // do nothing + } + } + + private async Task PollAsync(CancellationToken cancellationToken) + { + if (_adaptorImpl == null) + { + throw new InvalidOperationException("adaptor is not set."); + } + + var closeCode = int.MinValue; + try + { + var metaData = new Metadata { { "id", $"{_serviceContext.Id}" } }; + using var call = _adaptorImpl.Poll(metaData); + var request = new PollRequest(); + await call.RequestStream.WriteAsync(request); + while (await MoveAsync(call, cancellationToken) == true) + { + var reply = call.ResponseStream.Current; + if (reply.Code != int.MinValue) + { + closeCode = reply.Code; + break; + } + + InvokeCallback(reply); + await call.RequestStream.WriteAsync(request); + } + + await call.RequestStream.CompleteAsync(); + } + catch (Exception e) + { + closeCode = -2; + LogUtility.Error(e); + } + + if (closeCode != int.MinValue) + { + Disconnected?.Invoke(this, EventArgs.Empty); + } + + _serviceContext.Debug("Poll finished."); + } + + private void InvokeCallback(IService service, string name, string[] data) + { + if (_adaptorImpl == null) + { + throw new InvalidOperationException("adaptor is not set."); + } + + var methodDescriptors = _methodsByService[service]; + if (methodDescriptors.Contains(name) != true) + { + throw new InvalidOperationException("Invalid method name."); + } + + var methodDescriptor = methodDescriptors[name]; + var args = _serializer!.DeserializeMany(methodDescriptor.ParameterTypes, data); + var instance = _descriptor!.ClientInstances[service]; + Task.Run(() => methodDescriptor.InvokeAsync(_serviceContext, instance, args)); + } + + private void InvokeCallback(PollReply reply) + { + foreach (var item in reply.Items) + { + var service = _serviceByName[item.ServiceName]; + InvokeCallback(service, item.Name, [.. item.Data]); + } + + reply.Items.Clear(); + } + + private void HandleReply(InvokeReply reply) + { + if (reply.ID != string.Empty && Type.GetType(reply.ID) is { } exceptionType) + { + ThrowException(exceptionType, reply.Data); + } + } + + private void ThrowException(Type exceptionType, string data) + { + if (_serializer == null) + { + throw new InvalidOperationException("serializer is not set."); + } + + if (JsonConvert.DeserializeObject(data, exceptionType) is Exception exception) + { + throw exception; + } + + throw new UnreachableException( + $"This code should not be reached in {nameof(ThrowException)}."); + } } diff --git a/src/JSSoft.Communication/Grpc/AdaptorClientImpl.cs b/src/JSSoft.Communication/Grpc/AdaptorClientImpl.cs index ebd0026..2181f7e 100644 --- a/src/JSSoft.Communication/Grpc/AdaptorClientImpl.cs +++ b/src/JSSoft.Communication/Grpc/AdaptorClientImpl.cs @@ -1,30 +1,12 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using Grpc.Core; using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; +using Grpc.Core; #if NETSTANDARD using GrpcChannel = Grpc.Core.Channel; #elif NET @@ -33,7 +15,7 @@ namespace JSSoft.Communication.Grpc; -sealed class AdaptorClientImpl(GrpcChannel channel, Guid id, IService[] services) +internal sealed class AdaptorClientImpl(GrpcChannel channel, Guid id, IService[] services) : Adaptor.AdaptorClient(channel), IPeer { public string Id { get; } = $"{id}"; @@ -42,7 +24,6 @@ sealed class AdaptorClientImpl(GrpcChannel channel, Guid id, IService[] services public async Task OpenAsync(CancellationToken cancellationToken) { - var serviceNames = Services.Select(item => item.Name).ToArray(); var request = new OpenRequest(); var metaData = new Metadata() { { "id", $"{Id}" } }; try @@ -52,7 +33,10 @@ public async Task OpenAsync(CancellationToken cancellationToken) catch (RpcException e) { if (e.StatusCode == StatusCode.Cancelled) + { cancellationToken.ThrowIfCancellationRequested(); + } + throw; } } @@ -68,7 +52,10 @@ public async Task CloseAsync(CancellationToken cancellationToken) catch (RpcException e) { if (e.StatusCode == StatusCode.Cancelled) + { cancellationToken.ThrowIfCancellationRequested(); + } + throw; } } @@ -85,6 +72,7 @@ public async Task TryCloseAsync(CancellationToken cancellationToken) { return true; } + return false; } } diff --git a/src/JSSoft.Communication/Grpc/AdaptorServer.cs b/src/JSSoft.Communication/Grpc/AdaptorServer.cs index 5406758..ca0c0ed 100644 --- a/src/JSSoft.Communication/Grpc/AdaptorServer.cs +++ b/src/JSSoft.Communication/Grpc/AdaptorServer.cs @@ -1,55 +1,37 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using Grpc.Core; -using JSSoft.Communication.Logging; -using JSSoft.Communication.Extensions; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using Grpc.Core; +using JSSoft.Communication.Extensions; +using JSSoft.Communication.Logging; #if NET using System.Diagnostics; using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Logging; #endif namespace JSSoft.Communication.Grpc; -record struct CallbackData(IService Service, string Name, string[] Data); - -sealed class AdaptorServer : IAdaptor +internal sealed class AdaptorServer : IAdaptor { private static readonly TimeSpan Timeout = new(0, 0, 30); private static readonly TimeSpan PollTimeout = new(0, 0, 10); private readonly IServiceContext _serviceContext; private readonly IReadOnlyDictionary _serviceByName; private readonly Dictionary _methodsByService; + private readonly Timer _timer; #if NETSTANDARD private Server? _server; private AdaptorServerImpl? _adaptor; @@ -57,41 +39,54 @@ sealed class AdaptorServer : IAdaptor private IHost? _host; #endif private ISerializer? _serializer; - private readonly Timer _timer; private EventHandler? _disconnectedEventHandler; public AdaptorServer(IServiceContext serviceContext, IInstanceContext instanceContext) { _serviceContext = serviceContext; _serviceByName = serviceContext.Services; - _methodsByService = _serviceByName.ToDictionary(item => item.Value, item => new MethodDescriptorCollection(item.Value.ServerType)); + _methodsByService = _serviceByName.ToDictionary( + keySelector: item => item.Value, + elementSelector: item => new MethodDescriptorCollection(item.Value.ServerType)); Peers = new PeerCollection(instanceContext); _timer = new Timer(Timer_TimerCallback, null, TimeSpan.Zero, Timeout); } + event EventHandler? IAdaptor.Disconnected + { + add => _disconnectedEventHandler += value; + remove => _disconnectedEventHandler -= value; + } + public PeerCollection Peers { get; } public Guid Id => _serviceContext.Id; - public async Task OpenAsync(OpenRequest request, ServerCallContext context, CancellationToken cancellationToken) + public async Task OpenAsync( + OpenRequest request, ServerCallContext context, CancellationToken cancellationToken) { var id = GetId(context); if (Peers.TryGetValue(id, out var peer) == true) + { throw new InvalidOperationException($"The peer '{id}' already exists."); + } Peers.Add(_serviceContext, id); - await Task.CompletedTask; + await Task.Delay(1, cancellationToken); return new OpenReply(); } - public async Task CloseAsync(CloseRequest request, ServerCallContext context, CancellationToken cancellationToken) + public async Task CloseAsync( + CloseRequest request, ServerCallContext context, CancellationToken cancellationToken) { var id = GetId(context); if (Peers.TryGetValue(id, out var peer) != true) + { throw new InvalidOperationException($"The peer '{id}' does not exists."); + } Peers.Remove(_serviceContext, id, closeCode: 0); - await Task.CompletedTask; + await Task.Delay(1, cancellationToken); return new CloseReply(); } @@ -100,7 +95,9 @@ public async Task PingAsync(PingRequest request, ServerCallContext co var id = GetId(context); var dateTime = DateTime.UtcNow; if (Peers.TryGetValue(id, out var peer) != true) + { throw new InvalidOperationException($"The peer '{id}' does not exists."); + } peer.PingTime = dateTime; _serviceContext.Debug($"{id} Ping({dateTime})"); @@ -111,53 +108,74 @@ public async Task PingAsync(PingRequest request, ServerCallContext co public async Task InvokeAsync(InvokeRequest request, ServerCallContext context) { if (_serializer == null) + { throw new InvalidOperationException("Serializer is not set."); - if (_serviceByName.ContainsKey(request.ServiceName) != true) - throw new InvalidOperationException($"Service '{request.ServiceName}' does not exists."); - var service = _serviceByName[request.ServiceName]; + } + + var serviceName = request.ServiceName; + if (_serviceByName.TryGetValue(serviceName, out var service) != true) + { + throw new InvalidOperationException( + $"Service '{serviceName}' does not exists."); + } + var methodDescriptors = _methodsByService[service]; if (methodDescriptors.Contains(request.Name) != true) + { throw new InvalidOperationException($"Method '{request.Name}' does not exists."); + } var id = GetId(context); if (Peers.TryGetValue(id, out var peer) != true) + { throw new InvalidOperationException($"The peer '{id}' does not exists."); + } var methodDescriptor = methodDescriptors[request.Name]; - var cancellationToken = methodDescriptor.IsCancelable == true ? (CancellationToken?)context.CancellationToken : null; + var isCancelable = methodDescriptor.IsCancelable; + var cancellationToken = isCancelable ? (CancellationToken?)context.CancellationToken : null; var instance = peer.Services[service]; - var args = _serializer.DeserializeMany(methodDescriptor.ParameterTypes, [.. request.Data], cancellationToken); + var args = _serializer.DeserializeMany( + types: methodDescriptor.ParameterTypes, + datas: [.. request.Data], + cancellationToken: cancellationToken); if (methodDescriptor.IsOneWay == true) { - methodDescriptor.InvokeOneWay(_serviceContext, instance, args); var reply = new InvokeReply() { ID = string.Empty, - Data = _serializer.Serialize(typeof(void), null) + Data = _serializer.Serialize(typeof(void), null), }; + var methodShortName = methodDescriptor.ShortName; - _serviceContext.Debug($"{id} Invoke(one way): {request.ServiceName}.{methodDescriptor.ShortName}"); + methodDescriptor.InvokeOneWay(_serviceContext, instance, args); + _serviceContext.Debug($"{id} Invoke(one way): {serviceName}.{methodShortName}"); return reply; } else { - var (assemblyQualifiedName, valueType, value) = await methodDescriptor.InvokeAsync(_serviceContext, instance, args); + var result = await methodDescriptor.InvokeAsync(_serviceContext, instance, args); var reply = new InvokeReply() { - ID = $"{assemblyQualifiedName}", - Data = _serializer.Serialize(valueType, value) + ID = result.AssemblyQualifiedName, + Data = _serializer.Serialize(result.ValueType, result.Value), }; - _serviceContext.Debug($"{id} Invoke: {request.ServiceName}.{methodDescriptor.ShortName}"); + _serviceContext.Debug($"{id} Invoke: {methodDescriptor.Name}"); return reply; } } - public async Task PollAsync(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) + public async Task PollAsync( + IAsyncStreamReader requestStream, + IServerStreamWriter responseStream, + ServerCallContext context) { var id = GetId(context); if (Peers.TryGetValue(id, out var peer) != true) + { throw new InvalidOperationException($"The peer '{id}' does not exists."); + } using var manualResetEvent = new ManualResetEvent(initialState: true); var cancellationToken = peer.BeginPolling(manualResetEvent); @@ -168,9 +186,15 @@ public async Task PollAsync(IAsyncStreamReader requestStream, IServ var reply = peer.Collect(); await responseStream.WriteAsync(reply); if (cancellationToken.IsCancellationRequested == true) + { break; + } + if (peer.CanCollect == true) + { continue; + } + manualResetEvent.Reset(); manualResetEvent.WaitOne(PollTimeout); } @@ -179,6 +203,7 @@ public async Task PollAsync(IAsyncStreamReader requestStream, IServ { _serviceContext.Error(e.Message); } + peer.EndPolling(); _serviceContext.Debug("Poll finished."); @@ -195,44 +220,6 @@ public async ValueTask DisposeAsync() GC.SuppressFinalize(this); } - private static string GetId(ServerCallContext context) - { - if (context.RequestHeaders.Get("id") is { } entry) - { - return entry.Value; - } - - throw new ArgumentException("The id is not found."); - } - - private void AddCallback(InstanceBase instance, string name, Type[] types, object?[] args) - { - if (_serializer == null) - throw new UnreachableException("Serializer is not set."); - - var data = _serializer.SerializeMany(types, args); - var peers = instance.Peer is not Peer peer ? Peers.ToArray().Select(item => item.Value) : new Peer[] { peer }; - var service = instance.Service; - var callbackData = new CallbackData(service, name, data); - Parallel.ForEach(peers, item => item.AddCallback(callbackData)); - } - - private void Timer_TimerCallback(object? state) - { - var dateTime = DateTime.UtcNow; - var peers = Peers.ToArray(); - var query = from item in peers - let peer = item.Value - where dateTime - peer.PingTime > Timeout - select peer; - foreach (var item in query) - { - Peers.Remove(_serviceContext, item.Id, closeCode: -1); - } - } - - #region IAdaptor - #if NETSTANDARD async Task IAdaptor.OpenAsync(EndPoint endPoint, CancellationToken cancellationToken) { @@ -312,36 +299,73 @@ async Task IAdaptor.CloseAsync(CancellationToken cancellationToken) } #endif - void IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] args) + void IAdaptor.Invoke(InvokeOptions options) { - AddCallback(instance, name, types, args); + AddCallback(options); } - void IAdaptor.InvokeOneWay(InstanceBase instance, string name, Type[] types, object?[] args) + void IAdaptor.InvokeOneWay(InvokeOptions options) { - AddCallback(instance, name, types, args); + AddCallback(options); } - T IAdaptor.Invoke(InstanceBase instance, string name, Type[] types, object?[] args) + T IAdaptor.Invoke(InvokeOptions options) { - throw new NotSupportedException($"This method '{nameof(IAdaptor.Invoke)}' is not supported."); + throw new NotSupportedException( + $"This method '{nameof(IAdaptor.Invoke)}' is not supported."); } - Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken) + Task IAdaptor.InvokeAsync(InvokeOptions options, CancellationToken cancellationToken) { - throw new NotSupportedException($"This method '{nameof(IAdaptor.InvokeAsync)}' is not supported."); + throw new NotSupportedException( + $"This method '{nameof(IAdaptor.InvokeAsync)}' is not supported."); } - Task IAdaptor.InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken) + Task IAdaptor.InvokeAsync(InvokeOptions options, CancellationToken cancellationToken) { - throw new NotSupportedException($"This method '{nameof(IAdaptor.InvokeAsync)}' is not supported."); + throw new NotSupportedException( + $"This method '{nameof(IAdaptor.InvokeAsync)}' is not supported."); } - event EventHandler? IAdaptor.Disconnected + private static string GetId(ServerCallContext context) { - add => _disconnectedEventHandler += value; - remove => _disconnectedEventHandler -= value; + if (context.RequestHeaders.Get("id") is { } entry) + { + return entry.Value; + } + + throw new ArgumentException("The id is not found."); } - #endregion + private void AddCallback(InvokeOptions options) + { + if (_serializer == null) + { + throw new UnreachableException("Serializer is not set."); + } + + var instance = options.Instance; + var name = options.Name; + var types = options.Types; + var args = options.Args; + var data = _serializer.SerializeMany(types, args); + var peers = instance.Peer is not Peer peer ? Peers.Select(item => item.Value) : [peer]; + var service = instance.Service; + var callbackData = new CallbackData(service, name, data); + Parallel.ForEach(peers, item => item.AddCallback(callbackData)); + } + + private void Timer_TimerCallback(object? state) + { + var dateTime = DateTime.UtcNow; + var peers = Peers.ToArray(); + var query = from item in peers + let peer = item.Value + where dateTime - peer.PingTime > Timeout + select peer; + foreach (var item in query) + { + Peers.Remove(_serviceContext, item.Id, closeCode: -1); + } + } } diff --git a/src/JSSoft.Communication/Grpc/AdaptorServerImpl.cs b/src/JSSoft.Communication/Grpc/AdaptorServerImpl.cs index 03450f7..160e08e 100644 --- a/src/JSSoft.Communication/Grpc/AdaptorServerImpl.cs +++ b/src/JSSoft.Communication/Grpc/AdaptorServerImpl.cs @@ -1,31 +1,14 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using Grpc.Core; using System.Threading.Tasks; +using Grpc.Core; namespace JSSoft.Communication.Grpc; -sealed class AdaptorServerImpl(AdaptorServer adaptorServer) : Adaptor.AdaptorBase +internal sealed class AdaptorServerImpl(AdaptorServer adaptorServer) : Adaptor.AdaptorBase { private readonly AdaptorServer _adaptorServer = adaptorServer; @@ -41,6 +24,9 @@ public override Task Ping(PingRequest request, ServerCallContext cont public override Task Invoke(InvokeRequest request, ServerCallContext context) => _adaptorServer.InvokeAsync(request, context); - public override Task Poll(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) + public override Task Poll( + IAsyncStreamReader requestStream, + IServerStreamWriter responseStream, + ServerCallContext context) => _adaptorServer.PollAsync(requestStream, responseStream, context); } diff --git a/src/JSSoft.Communication/Grpc/CallbackData.cs b/src/JSSoft.Communication/Grpc/CallbackData.cs new file mode 100644 index 0000000..a0aa55c --- /dev/null +++ b/src/JSSoft.Communication/Grpc/CallbackData.cs @@ -0,0 +1,8 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +namespace JSSoft.Communication.Grpc; + +internal record struct CallbackData(IService Service, string Name, string[] Data); diff --git a/src/JSSoft.Communication/Grpc/Peer.cs b/src/JSSoft.Communication/Grpc/Peer.cs index 886f4dc..320cb49 100644 --- a/src/JSSoft.Communication/Grpc/Peer.cs +++ b/src/JSSoft.Communication/Grpc/Peer.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections.Generic; @@ -26,7 +9,7 @@ namespace JSSoft.Communication.Grpc; -sealed class Peer(string id) : IPeer +internal sealed class Peer(string id) : IPeer { private readonly object _lockObject = new(); private readonly List _callbackDataList = []; @@ -43,6 +26,8 @@ sealed class Peer(string id) : IPeer public int CloseCode { get; private set; } = int.MinValue; + public bool CanCollect => _callbackDataList.Count > 0; + public CancellationToken BeginPolling(ManualResetEvent manualResetEvent) { lock (_lockObject) @@ -68,6 +53,7 @@ public void Disconect(int closeCode) { CloseCode = closeCode; _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; _manualResetEvent?.Set(); } @@ -91,11 +77,10 @@ public PollReply Collect() Data = { item.Data }, }); } + return reply; } - public bool CanCollect => _callbackDataList.Count > 0; - public void AddCallback(CallbackData callbackData) { lock (_lockObject) @@ -115,6 +100,7 @@ private CallbackData[] Flush() _callbackDataList.Clear(); return items; } + return []; } } diff --git a/src/JSSoft.Communication/Grpc/PeerCollection.cs b/src/JSSoft.Communication/Grpc/PeerCollection.cs index 07c5eec..32a8be8 100644 --- a/src/JSSoft.Communication/Grpc/PeerCollection.cs +++ b/src/JSSoft.Communication/Grpc/PeerCollection.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections.Concurrent; @@ -30,7 +13,7 @@ namespace JSSoft.Communication.Grpc; -sealed class PeerCollection(IInstanceContext instanceContext) +internal sealed class PeerCollection(IInstanceContext instanceContext) : ConcurrentDictionary { private readonly IInstanceContext _instanceContext = instanceContext; @@ -59,10 +42,12 @@ public bool Remove(IServiceContext serviceContext, string id, int closeCode) serviceContext.Debug($"{id} Disconnected ({closeCode})"); return true; } + return false; } - public async Task DisconnectAsync(IServiceContext serviceContext, CancellationToken cancellationToken) + public async Task DisconnectAsync( + IServiceContext serviceContext, CancellationToken cancellationToken) { var items = Values.ToArray(); using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 3000); @@ -70,10 +55,12 @@ public async Task DisconnectAsync(IServiceContext serviceContext, CancellationTo { item.Disconect(closeCode: 0); } + while (Count > 0 && cancellationTokenSource.IsCancellationRequested != true) { await Task.Delay(1, cancellationToken); } + Clear(); } } diff --git a/src/JSSoft.Communication/IAdaptor.cs b/src/JSSoft.Communication/IAdaptor.cs index 92956b9..0c37c31 100644 --- a/src/JSSoft.Communication/IAdaptor.cs +++ b/src/JSSoft.Communication/IAdaptor.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Net; @@ -29,19 +12,19 @@ namespace JSSoft.Communication; public interface IAdaptor : IAsyncDisposable { + event EventHandler? Disconnected; + Task OpenAsync(EndPoint endPoint, CancellationToken cancellationToken); Task CloseAsync(CancellationToken cancellationToken); - void InvokeOneWay(InstanceBase instance, string name, Type[] types, object?[] args); + void InvokeOneWay(InvokeOptions options); - void Invoke(InstanceBase instance, string name, Type[] types, object?[] args); + void Invoke(InvokeOptions options); - T Invoke(InstanceBase instance, string name, Type[] types, object?[] args); + T Invoke(InvokeOptions options); - Task InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken); + Task InvokeAsync(InvokeOptions options, CancellationToken cancellationToken); - Task InvokeAsync(InstanceBase instance, string name, Type[] types, object?[] args, CancellationToken cancellationToken); - - event EventHandler? Disconnected; + Task InvokeAsync(InvokeOptions options, CancellationToken cancellationToken); } diff --git a/src/JSSoft.Communication/IAdaptorProvider.cs b/src/JSSoft.Communication/IAdaptorProvider.cs index 92c4eda..8b55ae9 100644 --- a/src/JSSoft.Communication/IAdaptorProvider.cs +++ b/src/JSSoft.Communication/IAdaptorProvider.cs @@ -1,30 +1,16 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication; public interface IAdaptorProvider { - IAdaptor Create(IServiceContext serviceContext, IInstanceContext instanceContext, ServiceToken serviceToken); - string Name { get; } + + IAdaptor Create( + IServiceContext serviceContext, + IInstanceContext instanceContext, + ServiceToken serviceToken); } diff --git a/src/JSSoft.Communication/IInstanceContext.cs b/src/JSSoft.Communication/IInstanceContext.cs index 011375e..6419a62 100644 --- a/src/JSSoft.Communication/IInstanceContext.cs +++ b/src/JSSoft.Communication/IInstanceContext.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication; diff --git a/src/JSSoft.Communication/ILGeneratorExtensions.cs b/src/JSSoft.Communication/ILGeneratorExtensions.cs index 3d927e5..47a2512 100644 --- a/src/JSSoft.Communication/ILGeneratorExtensions.cs +++ b/src/JSSoft.Communication/ILGeneratorExtensions.cs @@ -1,40 +1,30 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System.Reflection.Emit; namespace JSSoft.Communication; -static class ILGeneratorExtensions +internal static class ILGeneratorExtensions { - private static readonly OpCode[] Ldc_I4 = [ OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3, - OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8 ]; - private static readonly OpCode[] Ldarg = [OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3]; + private static readonly OpCode[] LdcI4 = + [ + OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3, + OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8, + ]; + + private static readonly OpCode[] Ldarg = + [ + OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3, + ]; public static void EmitLdc_I4(this ILGenerator il, int n) { - if (n < Ldc_I4.Length) + if (n < LdcI4.Length) { - il.Emit(Ldc_I4[n]); + il.Emit(LdcI4[n]); } else { diff --git a/src/JSSoft.Communication/IPeer.cs b/src/JSSoft.Communication/IPeer.cs index 6b49611..d8ec6c3 100644 --- a/src/JSSoft.Communication/IPeer.cs +++ b/src/JSSoft.Communication/IPeer.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication; diff --git a/src/JSSoft.Communication/ISerializer.cs b/src/JSSoft.Communication/ISerializer.cs index 0ea92f7..f35bf2c 100644 --- a/src/JSSoft.Communication/ISerializer.cs +++ b/src/JSSoft.Communication/ISerializer.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; diff --git a/src/JSSoft.Communication/ISerializerProvider.cs b/src/JSSoft.Communication/ISerializerProvider.cs index e994335..058308f 100644 --- a/src/JSSoft.Communication/ISerializerProvider.cs +++ b/src/JSSoft.Communication/ISerializerProvider.cs @@ -1,30 +1,13 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication; public interface ISerializerProvider { - ISerializer Create(IServiceContext serviceContext); - string Name { get; } + + ISerializer Create(IServiceContext serviceContext); } diff --git a/src/JSSoft.Communication/IService.cs b/src/JSSoft.Communication/IService.cs index bfb77df..b8f624e 100644 --- a/src/JSSoft.Communication/IService.cs +++ b/src/JSSoft.Communication/IService.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; diff --git a/src/JSSoft.Communication/IServiceContext.cs b/src/JSSoft.Communication/IServiceContext.cs index 20c4658..39eb5ae 100644 --- a/src/JSSoft.Communication/IServiceContext.cs +++ b/src/JSSoft.Communication/IServiceContext.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections.Generic; @@ -30,6 +13,16 @@ namespace JSSoft.Communication; public interface IServiceContext : IServiceProvider { + event EventHandler? Opened; + + event EventHandler? Closed; + + event EventHandler? Faulted; + + event EventHandler? Disconnected; + + event EventHandler? ServiceStateChanged; + IReadOnlyDictionary Services { get; } EndPoint EndPoint { get; set; } @@ -43,14 +36,4 @@ public interface IServiceContext : IServiceProvider Task CloseAsync(Guid token, CancellationToken cancellationToken); Task AbortAsync(); - - event EventHandler? Opened; - - event EventHandler? Closed; - - event EventHandler? Faulted; - - event EventHandler? Disconnected; - - event EventHandler? ServiceStateChanged; } diff --git a/src/JSSoft.Communication/InstanceBase.cs b/src/JSSoft.Communication/InstanceBase.cs index bcd4bdb..dbf3259 100644 --- a/src/JSSoft.Communication/InstanceBase.cs +++ b/src/JSSoft.Communication/InstanceBase.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Reflection; @@ -41,6 +24,8 @@ public abstract class InstanceBase private IService? _service; private IPeer? _peer; + internal static InstanceBase Empty { get; } = new Instance(); + internal IAdaptor Adaptor { get => _adaptor ?? throw new InvalidOperationException("adaptor is not set."); @@ -61,60 +46,118 @@ internal IPeer Peer set => _peer = value; } - internal static InstanceBase Empty { get; } = new Instance(); - [InstanceMethod(InvokeMethod)] protected void Invoke(string name, Type[] types, object?[] args) - => Adaptor.Invoke(this, name, types, args); - - protected void Invoke((string name, Type[] types, object?[] args) info) - => Adaptor.Invoke(this, info.name, info.types, info.args); - - [InstanceMethod(InvokeOneWayMethod)] - protected void InvokeOneWay(string name, Type[] types, object?[] args) - => Adaptor.InvokeOneWay(this, name, types, args); - - protected void InvokeOneWay((string name, Type[] types, object?[] args) info) - => Adaptor.Invoke(this, info.name, info.types, info.args); + => Adaptor.Invoke(new InvokeOptions + { + Instance = this, + Name = name, + Types = types, + Args = args, + }); + + protected void Invoke((string Name, Type[] Types, object?[] Args) info) + => Adaptor.Invoke(new InvokeOptions + { + Instance = this, + Name = info.Name, + Types = info.Types, + Args = info.Args, + }); [InstanceMethod(InvokeGenericMethod)] protected T Invoke(string name, Type[] types, object?[] args) - => Adaptor.Invoke(this, name, types, args); + => Adaptor.Invoke(new InvokeOptions + { + Instance = this, + Name = name, + Types = types, + Args = args, + }); + + protected T Invoke((string Name, Type[] Types, object?[] Args) info) + => Adaptor.Invoke(new InvokeOptions + { + Instance = this, + Name = info.Name, + Types = info.Types, + Args = info.Args, + }); - protected T Invoke((string name, Type[] types, object?[] args) info) - => Adaptor.Invoke(this, info.name, info.types, info.args); + [InstanceMethod(InvokeOneWayMethod)] + protected void InvokeOneWay(string name, Type[] types, object?[] args) + => Adaptor.InvokeOneWay(new InvokeOptions + { + Instance = this, + Name = name, + Types = types, + Args = args, + }); + + protected void InvokeOneWay((string Name, Type[] Types, object?[] Args) info) + => Adaptor.Invoke(new InvokeOptions + { + Instance = this, + Name = info.Name, + Types = info.Types, + Args = info.Args, + }); [InstanceMethod(InvokeAsyncMethod)] - protected Task InvokeAsync(string name, Type[] types, object?[] args, CancellationToken cancellationToken) - => Adaptor.InvokeAsync(this, name, types, args, cancellationToken); + protected Task InvokeAsync( + string name, Type[] types, object?[] args, CancellationToken cancellationToken) + { + var options = new InvokeOptions + { + Instance = this, + Name = name, + Types = types, + Args = args, + }; + return Adaptor.InvokeAsync(options, cancellationToken); + } - protected Task InvokeAsync((string name, Type[] types, object?[] args) info, CancellationToken cancellationToken) - => Adaptor.InvokeAsync(this, info.name, info.types, info.args, cancellationToken); + protected Task InvokeAsync( + (string Name, Type[] Types, object?[] Args) info, CancellationToken cancellationToken) + { + var options = new InvokeOptions + { + Instance = this, + Name = info.Name, + Types = info.Types, + Args = info.Args, + }; + return Adaptor.InvokeAsync(options, cancellationToken); + } [InstanceMethod(InvokeGenericAsyncMethod)] - protected Task InvokeAsync(string name, Type[] types, object?[] args, CancellationToken cancellationToken) - => Adaptor.InvokeAsync(this, name, types, args, cancellationToken); - - protected Task InvokeAsync((string name, Type[] types, object?[] args) info, CancellationToken cancellationToken) - => Adaptor.InvokeAsync(this, info.name, info.types, info.args, cancellationToken); - - protected static (string, Type[], object?[]) Info

(MethodInfo methodInfo, Type serviceType, P arg) - => (MethodUtility.GenerateName(methodInfo, serviceType), [typeof(P)], [arg]); - - protected static (string, Type[], object?[]) Info(MethodInfo methodInfo, Type serviceType, P1 arg1, P2 arg2) - => (MethodUtility.GenerateName(methodInfo, serviceType), [typeof(P1), typeof(P2)], [arg1, arg2]); - - protected static (string, Type[], object?[]) Info(MethodInfo methodInfo, Type serviceType, P1 arg1, P2 arg2, P3 arg3) - => (MethodUtility.GenerateName(methodInfo, serviceType), [typeof(P1), typeof(P2), typeof(P3)], [arg1, arg2, arg3]); - - protected static (string, Type[], object?[]) Info(MethodInfo methodInfo, Type serviceType, P1 arg1, P2 arg2, P3 arg3, P4 arg4) - => (MethodUtility.GenerateName(methodInfo, serviceType), [typeof(P1), typeof(P2), typeof(P3), typeof(P4)], [arg1, arg2, arg3, arg4]); - - #region Instance + protected Task InvokeAsync( + string name, Type[] types, object?[] args, CancellationToken cancellationToken) + { + var options = new InvokeOptions + { + Instance = this, + Name = name, + Types = types, + Args = args, + }; + return Adaptor.InvokeAsync(options, cancellationToken); + } - sealed class Instance : InstanceBase + protected Task InvokeAsync( + (string Name, Type[] Types, object?[] Args) info, CancellationToken cancellationToken) { + var options = new InvokeOptions + { + Instance = this, + Name = info.Name, + Types = info.Types, + Args = info.Args, + }; + return Adaptor.InvokeAsync(options, cancellationToken); } - #endregion + private sealed class Instance : InstanceBase + { + } } diff --git a/src/JSSoft.Communication/InstanceCollection.cs b/src/JSSoft.Communication/InstanceCollection.cs index 112465a..5a52769 100644 --- a/src/JSSoft.Communication/InstanceCollection.cs +++ b/src/JSSoft.Communication/InstanceCollection.cs @@ -1,29 +1,12 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System.Collections.Generic; namespace JSSoft.Communication; -sealed class InstanceCollection : Dictionary +internal sealed class InstanceCollection : Dictionary { } diff --git a/src/JSSoft.Communication/InstanceContext.cs b/src/JSSoft.Communication/InstanceContext.cs index a509d99..279dc4d 100644 --- a/src/JSSoft.Communication/InstanceContext.cs +++ b/src/JSSoft.Communication/InstanceContext.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections.Concurrent; @@ -26,7 +9,7 @@ namespace JSSoft.Communication; -sealed class InstanceContext(ServiceContextBase serviceContext) +internal sealed class InstanceContext(ServiceContextBase serviceContext) : IInstanceContext, IPeer { private readonly ConcurrentDictionary _descriptorByPeer = new(); @@ -49,7 +32,6 @@ where ServiceContextBase.IsPerPeer(_serviceContext, item) != true public void ReleaseInstance() { - var isServer = ServiceContextBase.IsServer(_serviceContext); var query = from item in _serviceContext.Services.Values.Reverse() where ServiceContextBase.IsPerPeer(_serviceContext, item) != true select item; @@ -73,10 +55,12 @@ public PeerDescriptor CreateInstance(IPeer peer) } else { - var (service, callback) = (_descriptor.ServerInstances[item], _descriptor.ClientInstances[item]); + var service = _descriptor.ServerInstances[item]; + var callback = _descriptor.ClientInstances[item]; peerDescriptor.AddInstance(item, service, callback); } } + _descriptorByPeer.TryAdd(peer, peerDescriptor); return peerDescriptor; } @@ -84,7 +68,10 @@ public PeerDescriptor CreateInstance(IPeer peer) public void DestroyInstance(IPeer peer) { if (_descriptorByPeer.TryRemove(peer, out var peerDescriptor) == false) + { return; + } + foreach (var item in _serviceContext.Services.Values.Reverse()) { var isPerPeer = ServiceContextBase.IsPerPeer(_serviceContext, item); @@ -98,15 +85,15 @@ public void DestroyInstance(IPeer peer) peerDescriptor.RemoveInstance(item); } } - peerDescriptor.Dispose(); + peerDescriptor.Dispose(); } public object? GetService(Type serviceType) { var query = from descriptor in _descriptorByPeer.Values from service in descriptor.ServerInstances.Values - where serviceType.IsAssignableFrom(service.GetType()) == true + where serviceType.IsInstanceOfType(service) == true select service; return query.SingleOrDefault(); } diff --git a/src/JSSoft.Communication/InstanceMethodAttribute.cs b/src/JSSoft.Communication/InstanceMethodAttribute.cs index d200b29..e170909 100644 --- a/src/JSSoft.Communication/InstanceMethodAttribute.cs +++ b/src/JSSoft.Communication/InstanceMethodAttribute.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; diff --git a/src/JSSoft.Communication/InvokeOptions.cs b/src/JSSoft.Communication/InvokeOptions.cs new file mode 100644 index 0000000..7763754 --- /dev/null +++ b/src/JSSoft.Communication/InvokeOptions.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; + +namespace JSSoft.Communication; + +public sealed record class InvokeOptions +{ + public required InstanceBase Instance { get; init; } + + public string Name { get; init; } = string.Empty; + + public Type[] Types { get; init; } = []; + + public object?[] Args { get; init; } = []; +} diff --git a/src/JSSoft.Communication/InvokeResult.cs b/src/JSSoft.Communication/InvokeResult.cs new file mode 100644 index 0000000..1c1f275 --- /dev/null +++ b/src/JSSoft.Communication/InvokeResult.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; + +namespace JSSoft.Communication; + +public sealed record class InvokeResult +{ + public required string AssemblyQualifiedName { get; init; } + + public required Type ValueType { get; init; } + + public object? Value { get; init; } +} diff --git a/src/JSSoft.Communication/JsonSerializer.cs b/src/JSSoft.Communication/JsonSerializer.cs index c787228..86b66b8 100644 --- a/src/JSSoft.Communication/JsonSerializer.cs +++ b/src/JSSoft.Communication/JsonSerializer.cs @@ -1,37 +1,20 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace JSSoft.Communication; -sealed class JsonSerializer : ISerializer +internal sealed class JsonSerializer : ISerializer { - private static readonly JsonSerializerSettings settings = new(); + private static readonly JsonSerializerSettings Settings = new(); public string Serialize(Type type, object? data) - => JsonConvert.SerializeObject(data, type, settings); + => JsonConvert.SerializeObject(data, type, Settings); public object? Deserialize(Type type, string text) - => JsonConvert.DeserializeObject(text, type, settings); + => JsonConvert.DeserializeObject(text, type, Settings); } diff --git a/src/JSSoft.Communication/JsonSerializerProvider.cs b/src/JSSoft.Communication/JsonSerializerProvider.cs index f795181..fadb296 100644 --- a/src/JSSoft.Communication/JsonSerializerProvider.cs +++ b/src/JSSoft.Communication/JsonSerializerProvider.cs @@ -1,35 +1,18 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication; -sealed class JsonSerializerProvider : ISerializerProvider +internal sealed class JsonSerializerProvider : ISerializerProvider { public const string DefaultName = "json"; - public ISerializer Create(IServiceContext serviceContext) - => new JsonSerializer(); + public static readonly JsonSerializerProvider Default = new(); public string Name => DefaultName; - public static readonly JsonSerializerProvider Default = new(); + public ISerializer Create(IServiceContext serviceContext) + => new JsonSerializer(); } diff --git a/src/JSSoft.Communication/Logging/ConsoleLogger.cs b/src/JSSoft.Communication/Logging/ConsoleLogger.cs index 1f0fddb..a17b431 100644 --- a/src/JSSoft.Communication/Logging/ConsoleLogger.cs +++ b/src/JSSoft.Communication/Logging/ConsoleLogger.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; @@ -26,6 +9,8 @@ namespace JSSoft.Communication.Logging; public sealed class ConsoleLogger : ILogger { + public static readonly ConsoleLogger Default = new(); + public void Debug(object message) => Console.WriteLine(message); public void Info(object message) => Console.WriteLine(message); @@ -35,6 +20,4 @@ public sealed class ConsoleLogger : ILogger public void Warn(object message) => Console.WriteLine(message); public void Fatal(object message) => Console.Error.WriteLine(message); - - public static readonly ConsoleLogger Default = new(); } diff --git a/src/JSSoft.Communication/Logging/EmptyLogger.cs b/src/JSSoft.Communication/Logging/EmptyLogger.cs index 922c411..3bb334e 100644 --- a/src/JSSoft.Communication/Logging/EmptyLogger.cs +++ b/src/JSSoft.Communication/Logging/EmptyLogger.cs @@ -1,29 +1,14 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Logging; public class EmptyLogger : ILogger { + public static readonly EmptyLogger Default = new(); + public void Debug(object message) { } @@ -43,6 +28,4 @@ public void Warn(object message) public void Fatal(object message) { } - - public static readonly EmptyLogger Default = new(); } diff --git a/src/JSSoft.Communication/Logging/ILogger.cs b/src/JSSoft.Communication/Logging/ILogger.cs index 2081175..3d8cb67 100644 --- a/src/JSSoft.Communication/Logging/ILogger.cs +++ b/src/JSSoft.Communication/Logging/ILogger.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Logging; diff --git a/src/JSSoft.Communication/Logging/ILoggerExtensions.cs b/src/JSSoft.Communication/Logging/ILoggerExtensions.cs index e24b65d..6f8b66b 100644 --- a/src/JSSoft.Communication/Logging/ILoggerExtensions.cs +++ b/src/JSSoft.Communication/Logging/ILoggerExtensions.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; diff --git a/src/JSSoft.Communication/Logging/LogLevel.cs b/src/JSSoft.Communication/Logging/LogLevel.cs index b1df4fc..1981a1a 100644 --- a/src/JSSoft.Communication/Logging/LogLevel.cs +++ b/src/JSSoft.Communication/Logging/LogLevel.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Logging; diff --git a/src/JSSoft.Communication/Logging/LogUtility.cs b/src/JSSoft.Communication/Logging/LogUtility.cs index f8cccd4..f224fae 100644 --- a/src/JSSoft.Communication/Logging/LogUtility.cs +++ b/src/JSSoft.Communication/Logging/LogUtility.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Logging; @@ -28,35 +11,45 @@ public static class LogUtility public static LogLevel LogLevel { get; set; } = LogLevel.Fatal; + private static ILogger LoggerInternal => Logger ?? EmptyLogger.Default; + public static void Debug(object message) { if (LogLevel >= LogLevel.Debug) + { LoggerInternal.Debug(message); + } } public static void Info(object message) { if (LogLevel >= LogLevel.Info) + { LoggerInternal.Info(message); + } } public static void Error(object message) { if (LogLevel >= LogLevel.Error) + { LoggerInternal.Error(message); + } } public static void Warn(object message) { if (LogLevel >= LogLevel.Warn) + { LoggerInternal.Warn(message); + } } public static void Fatal(object message) { if (LogLevel >= LogLevel.Fatal) + { LoggerInternal.Fatal(message); + } } - - private static ILogger LoggerInternal => Logger ?? EmptyLogger.Default; } diff --git a/src/JSSoft.Communication/Logging/TraceLogger.cs b/src/JSSoft.Communication/Logging/TraceLogger.cs index e9ea38c..5476dc2 100644 --- a/src/JSSoft.Communication/Logging/TraceLogger.cs +++ b/src/JSSoft.Communication/Logging/TraceLogger.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System.Diagnostics; @@ -26,7 +9,9 @@ namespace JSSoft.Communication.Logging; public sealed class TraceLogger : ILogger { - public void Debug(object message) => Trace.WriteLine(message); + public static readonly TraceLogger Default = new(); + + public void Debug(object message) => Trace.TraceInformation($"{message}"); public void Info(object message) => Trace.TraceInformation($"{message}"); @@ -35,6 +20,4 @@ public sealed class TraceLogger : ILogger public void Warn(object message) => Trace.TraceWarning($"{message}"); public void Fatal(object message) => Trace.TraceError($"{message}"); - - public static readonly TraceLogger Default = new(); } diff --git a/src/JSSoft.Communication/MethodDescriptor.cs b/src/JSSoft.Communication/MethodDescriptor.cs index 585cbbd..b80a9a1 100644 --- a/src/JSSoft.Communication/MethodDescriptor.cs +++ b/src/JSSoft.Communication/MethodDescriptor.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Linq; @@ -43,9 +26,10 @@ public MethodDescriptor(MethodInfo methodInfo) } else if (ReturnType.IsSubclassOf(typeof(Task)) == true) { - ReturnType = ReturnType.GetGenericArguments().First(); + ReturnType = ReturnType.GetGenericArguments()[0]; IsAsync = true; } + Name = GenerateName(methodInfo); ShortName = methodInfo.Name; IsOneWay = IsMethodOneWay(methodInfo); @@ -69,59 +53,103 @@ public MethodDescriptor(MethodInfo methodInfo) public MethodInfo MethodInfo { get; } - private static void Verify(MethodInfo methodInfo) + // "async" methods should not return "void" +#pragma warning disable S3168 + internal async void InvokeOneWay( + IServiceProvider serviceProvider, object instance, object?[] args) { - var parameterInfos = methodInfo.GetParameters(); - var isAsync = typeof(Task).IsAssignableFrom(methodInfo.ReturnType); - if (isAsync == true) - { - if (parameterInfos.Count(item => item.ParameterType == typeof(CancellationToken)) > 1) - throw new InvalidOperationException($"In method '{methodInfo}', only one parameter of type {nameof(CancellationToken)} should be defined."); - if (parameterInfos.Count(item => item.ParameterType == typeof(CancellationToken)) == 1 && - parameterInfos.Last().ParameterType != typeof(CancellationToken)) - throw new InvalidOperationException($"The last parameter of method '{methodInfo}' must be of type {nameof(CancellationToken)}."); - } - else if (methodInfo.ReturnType == typeof(void)) + try { - if (parameterInfos.Any(item => item.ParameterType == typeof(CancellationToken)) == true) - throw new InvalidOperationException($"The {nameof(CancellationToken)} type cannot be used in method '{methodInfo}'."); + await InvokeAsync(instance, args); } - else + catch { - throw new InvalidOperationException($"The return type of method '{methodInfo}' must be '{typeof(Task)}' or '{typeof(void)}'."); + // do nothing } } +#pragma warning restore S3168 - internal async void InvokeOneWay(IServiceProvider serviceProvider, object instance, object?[] args) + internal async Task InvokeAsync( + IServiceProvider serviceProvider, object instance, object?[] args) { try { - await InvokeAsync(instance, args); + var (valueType, value) = await InvokeAsync(instance, args); + return new InvokeResult + { + AssemblyQualifiedName = string.Empty, + ValueType = valueType, + Value = value, + }; } - catch + catch (TargetInvocationException e) { + var exception = e.InnerException ?? e; + return new InvokeResult + { + AssemblyQualifiedName = exception.GetType().AssemblyQualifiedName!, + ValueType = exception.GetType(), + Value = exception, + }; + } + catch (Exception e) + { + return new InvokeResult + { + AssemblyQualifiedName = e.GetType().AssemblyQualifiedName!, + ValueType = e.GetType(), + Value = e, + }; } } - internal async Task<(string, Type, object?)> InvokeAsync(IServiceProvider serviceProvider, object instance, object?[] args) + private static void Verify(MethodInfo methodInfo) { - try + var parameterInfos = methodInfo.GetParameters(); + var isAsync = typeof(Task).IsAssignableFrom(methodInfo.ReturnType); + if (isAsync == true) { - var (type, value) = await InvokeAsync(instance, args); - return (string.Empty, type, value); + if (parameterInfos.Count(item => item.ParameterType == typeof(CancellationToken)) > 1) + { + var message = $""" + In method '{methodInfo}', only one parameter of type + {nameof(CancellationToken)} should be defined. + """; + throw new InvalidOperationException(message); + } + + if (parameterInfos.Count(item => item.ParameterType == typeof(CancellationToken)) == 1 + && parameterInfos.Last().ParameterType != typeof(CancellationToken)) + { + var message = $""" + The last parameter of method '{methodInfo}' must be of + type {nameof(CancellationToken)}. + """; + throw new InvalidOperationException(message); + } } - catch (TargetInvocationException e) + else if (methodInfo.ReturnType == typeof(void)) { - var exception = e.InnerException ?? e; - return (exception.GetType().AssemblyQualifiedName!, exception.GetType(), exception); + if (parameterInfos.Any(item => item.ParameterType == typeof(CancellationToken)) == true) + { + var message = $""" + The {nameof(CancellationToken)} type cannot be used in method '{methodInfo}'. + """; + throw new InvalidOperationException(message); + } } - catch (Exception e) + else { - return (e.GetType().AssemblyQualifiedName!, e.GetType(), e); + var message = $""" + The return type of method '{methodInfo}' must be + '{typeof(Task)}' or '{typeof(void)}'. + """; + throw new InvalidOperationException(message); } } - private async Task<(Type, object?)> InvokeAsync(object? instance, object?[] args) + private async Task<(Type ValueType, object? Value)> InvokeAsync( + object? instance, object?[] args) { var value = await Task.Run(() => MethodInfo.Invoke(instance, args)); var valueType = MethodInfo.ReturnType; diff --git a/src/JSSoft.Communication/MethodDescriptorCollection.cs b/src/JSSoft.Communication/MethodDescriptorCollection.cs index 3b4a78f..bd98b86 100644 --- a/src/JSSoft.Communication/MethodDescriptorCollection.cs +++ b/src/JSSoft.Communication/MethodDescriptorCollection.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections; @@ -52,17 +35,13 @@ internal MethodDescriptorCollection(Type type) public int Count => _discriptorByName.Count; - public bool Contains(string name) => _discriptorByName.ContainsKey(name); - public MethodDescriptor this[string name] => _discriptorByName[name]; - #region IEnumerator + public bool Contains(string name) => _discriptorByName.ContainsKey(name); public IEnumerator GetEnumerator() => _discriptorByName.Values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _discriptorByName.Values.GetEnumerator(); - - #endregion } diff --git a/src/JSSoft.Communication/MethodUtility.cs b/src/JSSoft.Communication/MethodUtility.cs index 646f519..071fd82 100644 --- a/src/JSSoft.Communication/MethodUtility.cs +++ b/src/JSSoft.Communication/MethodUtility.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Linq; @@ -31,17 +14,28 @@ public static class MethodUtility { public static string GenerateName(MethodInfo methodInfo) { - var parameterTypes = methodInfo.GetParameters().Select(item => item.ParameterType).ToArray(); - return GenerateName(methodInfo.ReturnType, methodInfo.ReflectedType!, methodInfo.Name, parameterTypes); + var returnType = methodInfo.ReturnType; + var reflectedType = methodInfo.ReflectedType + ?? throw new ArgumentException("reflected type is null", nameof(methodInfo)); + var name = methodInfo.Name; + var parameterTypes = methodInfo.GetParameters() + .Select(item => item.ParameterType) + .ToArray(); + return GenerateName(returnType, reflectedType, name, parameterTypes); } public static string GenerateName(MethodInfo methodInfo, Type serviceType) { - var parameterTypes = methodInfo.GetParameters().Select(item => item.ParameterType).ToArray(); - return GenerateName(methodInfo.ReturnType, serviceType, methodInfo.Name, parameterTypes); + var returnType = methodInfo.ReturnType; + var name = methodInfo.Name; + var parameterTypes = methodInfo.GetParameters() + .Select(item => item.ParameterType) + .ToArray(); + return GenerateName(returnType, serviceType, name, parameterTypes); } - public static string GenerateName(Type returnType, Type reflectedType, string methodName, params Type[] parameterTypes) + public static string GenerateName( + Type returnType, Type reflectedType, string methodName, params Type[] parameterTypes) { var parameterTypeNames = string.Join(", ", parameterTypes); return $"{returnType} {reflectedType}.{methodName}({parameterTypeNames})"; @@ -55,8 +49,12 @@ public static bool IsMethodOneWay(MethodInfo methodInfo) public static bool IsMethodCancelable(MethodInfo methodInfo) { var parameterInfos = methodInfo.GetParameters(); - if (parameterInfos.Length > 0 && parameterInfos[parameterInfos.Length - 1].ParameterType == typeof(CancellationToken)) + if (parameterInfos.Length > 0 + && parameterInfos[^1].ParameterType == typeof(CancellationToken)) + { return true; + } + return false; } diff --git a/src/JSSoft.Communication/PeerDescriptor.cs b/src/JSSoft.Communication/PeerDescriptor.cs index 1d04ef8..f4a058f 100644 --- a/src/JSSoft.Communication/PeerDescriptor.cs +++ b/src/JSSoft.Communication/PeerDescriptor.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections.Generic; @@ -36,30 +19,34 @@ public sealed class PeerDescriptor : IDisposable public void Dispose() { - if (_isDisposed == true) - throw new ObjectDisposedException($"{this}"); + ObjectDisposedException.ThrowIf(_isDisposed, this); var items = ClientInstances.Values.OfType().ToArray(); foreach (var item in items) { item.Dispose(); } + _isDisposed = true; } public void AddInstance(IService service, object serverInstance, object clientInstance) { if (_isDisposed == true) + { throw new ObjectDisposedException($"{this}"); + } ServerInstances.Add(service, serverInstance); ClientInstances.Add(service, clientInstance); } - public (object serverInstance, object clientInstance) RemoveInstance(IService service) + public (object ServerInstance, object ClientInstance) RemoveInstance(IService service) { if (_isDisposed == true) + { throw new ObjectDisposedException($"{this}"); + } var value = (ServerInstances[service], ClientInstances[service]); ServerInstances.Remove(service); diff --git a/src/JSSoft.Communication/ServerContext.cs b/src/JSSoft.Communication/ServerContext.cs index 6777716..c0de9c5 100644 --- a/src/JSSoft.Communication/ServerContext.cs +++ b/src/JSSoft.Communication/ServerContext.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication; diff --git a/src/JSSoft.Communication/ServerService.cs b/src/JSSoft.Communication/ServerService.cs index e7d9e0a..a9a23e0 100644 --- a/src/JSSoft.Communication/ServerService.cs +++ b/src/JSSoft.Communication/ServerService.cs @@ -1,24 +1,10 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +// File may only contain a single type +#pragma warning disable SA1402 using System; @@ -40,18 +26,20 @@ public ServerService(TServer server) public ServerService() : base(typeof(TServer), typeof(TClient)) { - if (this is TServer server) + var obj = this; + if (obj is TServer server) { _server = server; - } else { - throw new InvalidOperationException($"'{GetType()}' must be implemented by '{typeof(TServer)}'."); + throw new InvalidOperationException( + $"'{GetType()}' must be implemented by '{typeof(TServer)}'."); } } - public TClient Client => _client ?? throw new InvalidOperationException("Client is not created."); + public TClient Client + => _client ?? throw new InvalidOperationException("Client is not created."); protected virtual TServer CreateServer(IPeer peer) => _server; @@ -85,13 +73,15 @@ public ServerService(TServer server) public ServerService() : base(serverType: typeof(TServer), clientType: typeof(void)) { - if (this is TServer server) + var obj = this; + if (obj is TServer server) { _server = server; } else { - throw new InvalidOperationException($"'{GetType()}' must be implemented by '{typeof(TServer)}'."); + throw new InvalidOperationException( + $"'{GetType()}' must be implemented by '{typeof(TServer)}'."); } } diff --git a/src/JSSoft.Communication/ServiceAttribute.cs b/src/JSSoft.Communication/ServiceAttribute.cs index 619fc43..70b04eb 100644 --- a/src/JSSoft.Communication/ServiceAttribute.cs +++ b/src/JSSoft.Communication/ServiceAttribute.cs @@ -1,31 +1,14 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; namespace JSSoft.Communication; [AttributeUsage(AttributeTargets.Class)] -sealed class ServiceAttribute : Attribute +internal sealed class ServiceAttribute : Attribute { public bool IsServer { get; set; } } diff --git a/src/JSSoft.Communication/ServiceBase.cs b/src/JSSoft.Communication/ServiceBase.cs index e4aaa81..78deca2 100644 --- a/src/JSSoft.Communication/ServiceBase.cs +++ b/src/JSSoft.Communication/ServiceBase.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; @@ -32,15 +15,11 @@ public abstract class ServiceBase(Type serverType, Type clientType) : IService public string Name { get; } = serverType.Name; - protected abstract object CreateInstance(IPeer peer, object obj); - - protected abstract void DestroyInstance(IPeer peer, object obj); - - #region IService - object IService.CreateInstance(IPeer peer, object obj) => CreateInstance(peer, obj); void IService.DestroyInstance(IPeer peer, object obj) => DestroyInstance(peer, obj); - #endregion + protected abstract object CreateInstance(IPeer peer, object obj); + + protected abstract void DestroyInstance(IPeer peer, object obj); } diff --git a/src/JSSoft.Communication/ServiceCollection.cs b/src/JSSoft.Communication/ServiceCollection.cs index 3ce341e..416e064 100644 --- a/src/JSSoft.Communication/ServiceCollection.cs +++ b/src/JSSoft.Communication/ServiceCollection.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System.Collections; using System.Collections.Generic; @@ -27,11 +10,11 @@ namespace JSSoft.Communication; -public class ServiceCollection(IEnumerable services) : IReadOnlyDictionary +public class ServiceCollection(IEnumerable services) + : IReadOnlyDictionary { - private readonly Dictionary _serviceByName = services.ToDictionary(item => item.Name); - - public IService this[string key] => _serviceByName[key]; + private readonly Dictionary _serviceByName + = services.ToDictionary(item => item.Name); public IEnumerable Keys => _serviceByName.Keys; @@ -39,6 +22,8 @@ public class ServiceCollection(IEnumerable services) : IReadOnlyDictio public int Count => _serviceByName.Count; + public IService this[string key] => _serviceByName[key]; + public bool ContainsKey(string key) => _serviceByName.ContainsKey(key); #if NETSTANDARD @@ -49,13 +34,9 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out IService value) => _serviceByName.TryGetValue(key, out value); #endif - #region IEnumerable - - IEnumerator> IEnumerable>.GetEnumerator() + public IEnumerator> GetEnumerator() => _serviceByName.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _serviceByName.GetEnumerator(); - - #endregion } diff --git a/src/JSSoft.Communication/ServiceContextAttribute.cs b/src/JSSoft.Communication/ServiceContextAttribute.cs index 07e88f9..3a81e83 100644 --- a/src/JSSoft.Communication/ServiceContextAttribute.cs +++ b/src/JSSoft.Communication/ServiceContextAttribute.cs @@ -1,31 +1,14 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; namespace JSSoft.Communication; [AttributeUsage(AttributeTargets.Class)] -sealed class ServiceContextAttribute : Attribute +internal sealed class ServiceContextAttribute : Attribute { public bool IsServer { get; set; } } diff --git a/src/JSSoft.Communication/ServiceContextBase.cs b/src/JSSoft.Communication/ServiceContextBase.cs index 373eb51..90f36f4 100644 --- a/src/JSSoft.Communication/ServiceContextBase.cs +++ b/src/JSSoft.Communication/ServiceContextBase.cs @@ -1,32 +1,15 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.Logging; using System; using System.Collections.Generic; using System.Net; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using JSSoft.Communication.Logging; namespace JSSoft.Communication; @@ -53,6 +36,19 @@ protected ServiceContextBase(IService[] services) _instanceContext = new InstanceContext(this); } + public event EventHandler? Opened; + + public event EventHandler? Closed; + + public event EventHandler? Faulted; + + ///

+ /// Disconnected event is not used on the server side. + /// + public event EventHandler? Disconnected; + + public event EventHandler? ServiceStateChanged; + public virtual IAdaptorProvider AdaptorProvider => Communication.AdaptorProvider.Default; public virtual ISerializerProvider SerializerProvider => JsonSerializerProvider.Default; @@ -66,9 +62,9 @@ private set { if (_serviceState != value) { - var _ = _serviceState; + var serviceState = _serviceState; _serviceState = value; - Debug($"{nameof(ServiceState)}.{_} => {nameof(ServiceState)}.{value}"); + Debug($"{nameof(ServiceState)}.{serviceState} => {nameof(ServiceState)}.{value}"); OnServiceStateChanged(EventArgs.Empty); } } @@ -80,19 +76,30 @@ public EndPoint EndPoint set { if (ServiceState != ServiceState.None) - throw new InvalidOperationException($"Cannot set '{nameof(EndPoint)}'. service state is '{ServiceState.None}'."); + { + var message = $""" + Cannot set '{nameof(EndPoint)}'. service state is '{ServiceState.None}'. + """; + throw new InvalidOperationException(message); + } + _endPoint = value; } } public Guid Id { get; } = Guid.NewGuid(); + IReadOnlyDictionary IServiceContext.Services => Services; + public override string ToString() => $"{_t}[{Id}]"; public async Task OpenAsync(CancellationToken cancellationToken) { if (ServiceState != ServiceState.None) - throw new InvalidOperationException($"Service can only be open if the state is '{ServiceState.None}'."); + { + var message = $"Service can only be open if the state is '{ServiceState.None}'."; + throw new InvalidOperationException(message); + } try { @@ -124,9 +131,15 @@ public async Task OpenAsync(CancellationToken cancellationToken) public async Task CloseAsync(Guid token, CancellationToken cancellationToken) { if (ServiceState != ServiceState.Open) - throw new InvalidOperationException($"Service can only be closed if the state is '{ServiceState.Open}'."); + { + throw new InvalidOperationException( + $"Service can only be closed if the state is '{ServiceState.Open}'."); + } + if (token == Guid.Empty || _token!.Guid != token) + { throw new ArgumentException($"'{token}' is an invalid token.", nameof(token)); + } try { @@ -158,7 +171,10 @@ public async Task CloseAsync(Guid token, CancellationToken cancellationToken) public async Task AbortAsync() { if (ServiceState != ServiceState.Faulted) - throw new InvalidOperationException($"Service can only be aborted if the state is '{ServiceState.Faulted}'."); + { + throw new InvalidOperationException( + $"Service can only be aborted if the state is '{ServiceState.Faulted}'."); + } _token = null; _serializer = null; @@ -170,6 +186,7 @@ public async Task AbortAsync() Debug($"{nameof(IAdaptor)} ({AdaptorProvider.Name}) disposed."); _adaptor = null; } + ServiceState = ServiceState.None; OnClosed(EventArgs.Empty); } @@ -177,57 +194,27 @@ public async Task AbortAsync() public virtual object? GetService(Type serviceType) { if (serviceType == typeof(ISerializer)) + { return _serializer; + } + if (_instanceContext.GetService(serviceType) is { } instanceService) + { return instanceService; - return null; - } - - public event EventHandler? Opened; - - public event EventHandler? Closed; - - public event EventHandler? Faulted; - - /// - /// Disconnected event is not used on the server side. - /// - public event EventHandler? Disconnected; - - public event EventHandler? ServiceStateChanged; + } - protected virtual InstanceBase CreateInstance(Type type) - { - if (_instanceBuilder == null) - throw new InvalidOperationException($"Cannot create instance of {type}"); - if (type == typeof(void)) - return InstanceBase.Empty; - var typeName = $"{type.Name}Impl"; - var instanceType = _instanceBuilder.CreateType(typeName, typeof(InstanceBase), type); - return (InstanceBase)Activator.CreateInstance(instanceType)!; + return null; } - protected virtual void OnOpened(EventArgs e) - => Opened?.Invoke(this, e); - - protected virtual void OnClosed(EventArgs e) - => Closed?.Invoke(this, e); - - protected virtual void OnFaulted(EventArgs e) - => Faulted?.Invoke(this, e); - - protected virtual void OnDisconnected(EventArgs e) - => Disconnected?.Invoke(this, e); - - protected virtual void OnServiceStateChanged(EventArgs e) - => ServiceStateChanged?.Invoke(this, e); - internal static bool IsServer(ServiceContextBase serviceContext) { - if (serviceContext.GetType().GetCustomAttribute(typeof(ServiceContextAttribute)) is ServiceContextAttribute attribute) + var serviceContextType = serviceContext.GetType(); + var attribute = serviceContextType.GetCustomAttribute(typeof(ServiceContextAttribute)); + if (attribute is ServiceContextAttribute serviceContextAttribute) { - return attribute.IsServer; + return serviceContextAttribute.IsServer; } + return false; } @@ -238,31 +225,37 @@ internal static Type GetInstanceType(ServiceContextBase serviceContext, IService { return service.ClientType; } + return service.ServerType; } internal static bool IsPerPeer(ServiceContextBase serviceContext, IService service) { if (IsServer(serviceContext) != true) + { return false; + } + var serviceType = service.ServerType; - if (serviceType.GetCustomAttribute(typeof(ServiceContractAttribute)) is ServiceContractAttribute attribute) + var attribute = serviceType.GetCustomAttribute(typeof(ServiceContractAttribute)); + if (attribute is ServiceContractAttribute serviceContractAttribute) { - return attribute.PerPeer; + return serviceContractAttribute.PerPeer; } + return false; } - internal (object, object) CreateInstance(IService service, IPeer peer) + internal (object ServiceInstance, object ClientInstance) CreateInstance( + IService service, IPeer peer) { var adaptor = _adaptor!; var baseType = GetInstanceType(this, service); var instance = CreateInstance(baseType); - { - instance.Service = service; - instance.Adaptor = adaptor; - instance.Peer = peer; - } + + instance.Service = service; + instance.Adaptor = adaptor; + instance.Peer = peer; var impl = service.CreateInstance(peer, instance); var serverInstance = _isServer ? impl : instance; @@ -270,7 +263,8 @@ internal static bool IsPerPeer(ServiceContextBase serviceContext, IService servi return (serverInstance, clientInstance); } - internal void DestroyInstance(IService service, IPeer peer, object serverInstance, object clientInstance) + internal void DestroyInstance( + IService service, IPeer peer, object serverInstance, object clientInstance) { if (_isServer == true) { @@ -282,6 +276,38 @@ internal void DestroyInstance(IService service, IPeer peer, object serverInstanc } } + protected virtual InstanceBase CreateInstance(Type type) + { + if (_instanceBuilder == null) + { + throw new InvalidOperationException($"Cannot create instance of {type}"); + } + + if (type == typeof(void)) + { + return InstanceBase.Empty; + } + + var typeName = $"{type.Name}Impl"; + var instanceType = _instanceBuilder.CreateType(typeName, typeof(InstanceBase), type); + return (InstanceBase)Activator.CreateInstance(instanceType)!; + } + + protected virtual void OnOpened(EventArgs e) + => Opened?.Invoke(this, e); + + protected virtual void OnClosed(EventArgs e) + => Closed?.Invoke(this, e); + + protected virtual void OnFaulted(EventArgs e) + => Faulted?.Invoke(this, e); + + protected virtual void OnDisconnected(EventArgs e) + => Disconnected?.Invoke(this, e); + + protected virtual void OnServiceStateChanged(EventArgs e) + => ServiceStateChanged?.Invoke(this, e); + private void Debug(string message) => LogUtility.Debug($"{this} {message}"); @@ -301,10 +327,4 @@ private void Adaptor_Disconnected(object? sender, EventArgs e) Debug($"{nameof(IServiceContext)} ({this.GetType()}) closed."); OnClosed(EventArgs.Empty); } - - #region IService - - IReadOnlyDictionary IServiceContext.Services => Services; - - #endregion } diff --git a/src/JSSoft.Communication/ServiceContractAttribute.cs b/src/JSSoft.Communication/ServiceContractAttribute.cs index e7df73b..6a2c9be 100644 --- a/src/JSSoft.Communication/ServiceContractAttribute.cs +++ b/src/JSSoft.Communication/ServiceContractAttribute.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; diff --git a/src/JSSoft.Communication/ServiceInstanceBuilder.cs b/src/JSSoft.Communication/ServiceInstanceBuilder.cs index f124f8b..571330e 100644 --- a/src/JSSoft.Communication/ServiceInstanceBuilder.cs +++ b/src/JSSoft.Communication/ServiceInstanceBuilder.cs @@ -1,24 +1,10 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +// Reflection should not be used to increase accessibility of classes, methods, or fields +#pragma warning disable S3011 using System; using System.Collections.Generic; @@ -30,18 +16,34 @@ namespace JSSoft.Communication; -sealed class ServiceInstanceBuilder +internal sealed class ServiceInstanceBuilder { - private const string ns = "JSSoft.Communication.Runtime"; + private const string Namespace = "JSSoft.Communication.Runtime"; private readonly Dictionary _typeByName = []; - private readonly AssemblyBuilder _assemblyBuilder; private readonly ModuleBuilder _moduleBuilder; internal ServiceInstanceBuilder() { - AssemblyName = new AssemblyName(ns); - _assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.RunAndCollect); - _moduleBuilder = _assemblyBuilder.DefineDynamicModule(AssemblyName.Name!); + var assemblyName = new AssemblyName(Namespace); + var access = AssemblyBuilderAccess.RunAndCollect; + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, access); + _moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name!); + AssemblyName = assemblyName; + } + + public AssemblyName AssemblyName { get; } + + public Type CreateType(string name, Type baseType, Type interfaceType) + { + var fullName = $"{AssemblyName}.{name}"; + if (_typeByName.TryGetValue(fullName, out var value) != true) + { + var type = CreateType(_moduleBuilder, name, baseType, interfaceType); + _typeByName.Add(fullName, type); + value = type; + } + + return value; } internal static ServiceInstanceBuilder? Create() @@ -56,22 +58,11 @@ internal ServiceInstanceBuilder() } } - public AssemblyName AssemblyName { get; } - - public Type CreateType(string name, Type baseType, Type interfaceType) - { - var fullName = $"{AssemblyName}.{name}"; - if (_typeByName.ContainsKey(fullName) != true) - { - var type = CreateType(_moduleBuilder, name, baseType, interfaceType); - _typeByName.Add(fullName, type); - } - return _typeByName[fullName]; - } - - private static Type CreateType(ModuleBuilder moduleBuilder, string typeName, Type baseType, Type interfaceType) + private static Type CreateType( + ModuleBuilder moduleBuilder, string typeName, Type baseType, Type interfaceType) { - var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, baseType, [interfaceType]); + var typeAttrs = TypeAttributes.Class | TypeAttributes.Public; + var typeBuilder = moduleBuilder.DefineType(typeName, typeAttrs, baseType, [interfaceType]); var methodInfos = interfaceType.GetMethods(); foreach (var methodInfo in methodInfos) { @@ -88,9 +79,13 @@ private static Type CreateType(ModuleBuilder moduleBuilder, string typeName, Typ else if (returnType == typeof(void)) { if (isOneWay == true) + { CreateInvoke(typeBuilder, methodInfo, InstanceBase.InvokeOneWayMethod); + } else + { CreateInvoke(typeBuilder, methodInfo, InstanceBase.InvokeMethod); + } } else { @@ -101,19 +96,26 @@ private static Type CreateType(ModuleBuilder moduleBuilder, string typeName, Typ return typeBuilder.CreateType(); } - private static void CreateInvoke(TypeBuilder typeBuilder, MethodInfo methodInfo, string methodName) + private static void CreateInvoke( + TypeBuilder typeBuilder, MethodInfo methodInfo, string methodName) { var parameterInfos = methodInfo.GetParameters(); var parameterTypes = parameterInfos.Select(i => i.ParameterType).ToArray(); var returnType = methodInfo.ReturnType; - var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig; - var methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, methodAttributes, CallingConventions.Standard, returnType, parameterTypes); + var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual + | MethodAttributes.Final | MethodAttributes.HideBySig; + var methodBuilder = typeBuilder.DefineMethod( + name: methodInfo.Name, + attributes: methodAttributes, + callingConvention: CallingConventions.Standard, + returnType: returnType, + parameterTypes: parameterTypes); var invokeMethod = FindInvokeMethod(typeBuilder.BaseType!, methodName, returnType); var typeofMethod = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!; for (var i = 0; i < parameterInfos.Length; i++) { - var pb = methodBuilder.DefineParameter(i, ParameterAttributes.Lcid, parameterInfos[i].Name); + methodBuilder.DefineParameter(i, ParameterAttributes.Lcid, parameterInfos[i].Name); } var il = methodBuilder.GetILGenerator(); @@ -123,18 +125,19 @@ private static void CreateInvoke(TypeBuilder typeBuilder, MethodInfo methodInfo, { il.DeclareLocal(returnType); } + il.Emit(OpCodes.Nop); il.EmitLdc_I4(parameterInfos.Length); il.Emit(OpCodes.Newarr, typeof(Type)); for (var i = 0; i < parameterInfos.Length; i++) { - var item = parameterInfos[i]; il.Emit(OpCodes.Dup); il.EmitLdc_I4(i); il.Emit(OpCodes.Ldtoken, parameterTypes[i]); il.Emit(OpCodes.Call, typeofMethod); il.Emit(OpCodes.Stelem_Ref); } + il.Emit(OpCodes.Stloc_0); il.EmitLdc_I4(parameterInfos.Length); il.Emit(OpCodes.Newarr, typeof(object)); @@ -148,8 +151,10 @@ private static void CreateInvoke(TypeBuilder typeBuilder, MethodInfo methodInfo, { il.Emit(OpCodes.Box, parameterTypes[i]); } + il.Emit(OpCodes.Stelem_Ref); } + il.Emit(OpCodes.Stloc_1); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldstr, MethodUtility.GenerateName(methodInfo)); @@ -160,14 +165,20 @@ private static void CreateInvoke(TypeBuilder typeBuilder, MethodInfo methodInfo, il.Emit(OpCodes.Ret); } - private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo methodInfo, string methodName) + private static void CreateInvokeAsync( + TypeBuilder typeBuilder, MethodInfo methodInfo, string methodName) { var isCancelable = MethodUtility.IsMethodCancelable(methodInfo); var parameterInfos = methodInfo.GetParameters(); var parameterTypes = parameterInfos.Select(i => i.ParameterType).ToArray(); var returnType = methodInfo.ReturnType; - var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig; - var methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, methodAttributes, returnType, parameterTypes); + var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual + | MethodAttributes.Final | MethodAttributes.HideBySig; + var methodBuilder = typeBuilder.DefineMethod( + name: methodInfo.Name, + attributes: methodAttributes, + returnType: returnType, + parameterTypes: parameterTypes); var invokeMethod = FindInvokeMethod(typeBuilder.BaseType!, methodName, returnType); var typeofMethod = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!; @@ -176,7 +187,9 @@ private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo method methodBuilder.DefineParameter(i, ParameterAttributes.None, parameterInfos[i].Name); } - var parameterLength = isCancelable == true ? parameterInfos.Length - 1 : parameterInfos.Length; + var parameterLength = isCancelable == true + ? parameterInfos.Length - 1 + : parameterInfos.Length; var il = methodBuilder.GetILGenerator(); il.DeclareLocal(typeof(Type[])); il.DeclareLocal(typeof(object[])); @@ -186,13 +199,13 @@ private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo method il.Emit(OpCodes.Newarr, typeof(Type)); for (var i = 0; i < parameterLength; i++) { - var item = parameterInfos[i]; il.Emit(OpCodes.Dup); il.EmitLdc_I4(i); il.Emit(OpCodes.Ldtoken, parameterTypes[i]); il.Emit(OpCodes.Call, typeofMethod); il.Emit(OpCodes.Stelem_Ref); } + il.Emit(OpCodes.Stloc_0); il.EmitLdc_I4(parameterLength); il.Emit(OpCodes.Newarr, typeof(object)); @@ -206,8 +219,10 @@ private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo method { il.Emit(OpCodes.Box, parameterTypes[i]); } + il.Emit(OpCodes.Stelem_Ref); } + il.Emit(OpCodes.Stloc_1); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldstr, MethodUtility.GenerateName(methodInfo)); @@ -219,8 +234,11 @@ private static void CreateInvokeAsync(TypeBuilder typeBuilder, MethodInfo method } else { - il.Emit(OpCodes.Call, typeof(CancellationToken).GetMethod("get_None", BindingFlags.Public | BindingFlags.Static)!); + var bindingFlags = BindingFlags.Public | BindingFlags.Static; + var meth = typeof(CancellationToken).GetMethod("get_None", bindingFlags)!; + il.Emit(OpCodes.Call, meth); } + il.Emit(OpCodes.Call, invokeMethod); il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ret); @@ -231,28 +249,22 @@ private static MethodInfo FindInvokeMethod(Type baseType, string methodName, Typ var methodInfos = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance); foreach (var item in methodInfos) { - if (item.GetCustomAttribute() is InstanceMethodAttribute attr && attr.MethodName == methodName) + var attribute = item.GetCustomAttribute(); + if (attribute is InstanceMethodAttribute instanceMethodAttribute + && instanceMethodAttribute.MethodName == methodName) { if (item.IsGenericMethod == true) { if (returnType.IsGenericType == true) + { return item.MakeGenericMethod(returnType.GetGenericArguments()); + } else + { return item.MakeGenericMethod(returnType); + } } - return item; - } - } - - throw new NotSupportedException($"'{methodName}' method is not found."); - } - private static MethodInfo FindInvokeMethod(MethodInfo[] methodInfos, string methodName) - { - foreach (var item in methodInfos) - { - if (item.GetCustomAttribute() is InstanceMethodAttribute attr && attr.MethodName == methodName) - { return item; } } diff --git a/src/JSSoft.Communication/ServiceState.cs b/src/JSSoft.Communication/ServiceState.cs index b5bd1bd..37ae8ab 100644 --- a/src/JSSoft.Communication/ServiceState.cs +++ b/src/JSSoft.Communication/ServiceState.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication; diff --git a/src/JSSoft.Communication/ServiceToken.cs b/src/JSSoft.Communication/ServiceToken.cs index ca1b8ce..48927d9 100644 --- a/src/JSSoft.Communication/ServiceToken.cs +++ b/src/JSSoft.Communication/ServiceToken.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; @@ -26,12 +9,11 @@ namespace JSSoft.Communication; public sealed class ServiceToken { + internal static readonly ServiceToken Empty = new(Guid.Empty); + internal ServiceToken(Guid guid) => Guid = guid; internal Guid Guid { get; } - internal static ServiceToken NewToken() - => new(Guid.NewGuid()); - - internal static readonly ServiceToken Empty = new(Guid.Empty); + internal static ServiceToken NewToken() => new(Guid.NewGuid()); } diff --git a/src/JSSoft.Communication/ServiceUtility.cs b/src/JSSoft.Communication/ServiceUtility.cs index da02394..cf4c6ec 100644 --- a/src/JSSoft.Communication/ServiceUtility.cs +++ b/src/JSSoft.Communication/ServiceUtility.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Reflection; @@ -27,26 +10,43 @@ namespace JSSoft.Communication; public static class ServiceUtility { - public static Type ValidateServerType(Type ServiceType) + public static Type ValidateServerType(Type serviceType) { - if (ServiceType.IsInterface != true) + if (serviceType.IsInterface != true) + { throw new InvalidOperationException("Server type must be interface."); + } + + if (IsNestedPublicType(serviceType) != true + && IsPublicType(serviceType) != true + && IsInternalType(serviceType) != true) + { + throw new InvalidOperationException( + $"'{serviceType.Name}' must be public or internal."); + } - if (IsNestedPublicType(ServiceType) != true && IsPublicType(ServiceType) != true && IsInternalType(ServiceType) != true) - throw new InvalidOperationException($"'{ServiceType.Name}' must be public or internal."); - return ServiceType; + return serviceType; } - public static Type ValidateClientType(Type CallbackType) + public static Type ValidateClientType(Type callbackType) { - if (CallbackType != typeof(void)) + if (callbackType != typeof(void)) { - if (CallbackType.IsInterface != true) + if (callbackType.IsInterface != true) + { throw new InvalidOperationException("Client type must be interface."); - if (IsNestedPublicType(CallbackType) != true && IsPublicType(CallbackType) != true && IsInternalType(CallbackType) != true) - throw new InvalidOperationException($"'{CallbackType.Name}' must be public or internal."); + } + + if (IsNestedPublicType(callbackType) != true + && IsPublicType(callbackType) != true + && IsInternalType(callbackType) != true) + { + throw new InvalidOperationException( + $"'{callbackType.Name}' must be public or internal."); + } } - return CallbackType; + + return callbackType; } public static bool IsNestedPublicType(Type type) @@ -60,10 +60,12 @@ public static bool IsInternalType(Type type) public static bool IsServer(IService service) { - if (service.GetType().GetCustomAttribute(typeof(ServiceAttribute)) is ServiceAttribute serviceAttribute) + var attribute = service.GetType().GetCustomAttribute(typeof(ServiceAttribute)); + if (attribute is ServiceAttribute serviceAttribute) { return serviceAttribute.IsServer; } + return false; } } diff --git a/src/JSSoft.Communication/TaskUtility.cs b/src/JSSoft.Communication/TaskUtility.cs index 50f7561..c024ec1 100644 --- a/src/JSSoft.Communication/TaskUtility.cs +++ b/src/JSSoft.Communication/TaskUtility.cs @@ -1,33 +1,17 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System.Threading; using System.Threading.Tasks; namespace JSSoft.Communication; -static class TaskUtility +internal static class TaskUtility { - public static async Task TryDelay(int millisecondsDelay, CancellationToken cancellationToken) + public static async Task TryDelay( + int millisecondsDelay, CancellationToken cancellationToken) { try { diff --git a/src/JSSoft.Communication/Threading/Dispatcher.cs b/src/JSSoft.Communication/Threading/Dispatcher.cs index 379d4cd..ec1cb0b 100644 --- a/src/JSSoft.Communication/Threading/Dispatcher.cs +++ b/src/JSSoft.Communication/Threading/Dispatcher.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Threading; @@ -33,22 +16,22 @@ public class Dispatcher : IDisposable private readonly CancellationToken _cancellationToken; private readonly DispatcherSynchronizationContext _context; private readonly DispatcherScheduler _scheduler; - private bool _isDisposed; - #if DEBUG - private readonly System.Diagnostics.StackTrace? _stackTrace; + private readonly System.Diagnostics.StackTrace _stackTrace; #endif + public Dispatcher(object owner) { _cancellationTokenSource = new CancellationTokenSource(); _cancellationToken = _cancellationTokenSource.Token; - _scheduler = new DispatcherScheduler(this, _cancellationToken); - _factory = new TaskFactory(_cancellationToken, TaskCreationOptions.None, TaskContinuationOptions.None, _scheduler); + _scheduler = new DispatcherScheduler(_cancellationToken); + _factory = new TaskFactory( + _cancellationToken, TaskCreationOptions.None, TaskContinuationOptions.None, _scheduler); _context = new DispatcherSynchronizationContext(_factory); Owner = owner; #if DEBUG _stackTrace = new System.Diagnostics.StackTrace(true); -#endif +#endif Thread = new Thread(_scheduler.Run) { Name = $"{owner}: {owner.GetHashCode()}", @@ -65,6 +48,10 @@ public Dispatcher(object owner) public SynchronizationContext SynchronizationContext => _context; +#if DEBUG + internal string StackTrace => $"{_stackTrace}"; +#endif + public override string ToString() => $"{Owner}"; public void VerifyAccess() @@ -79,22 +66,35 @@ public void VerifyAccess() public void Invoke(Action action) { - if (_cancellationToken.IsCancellationRequested == true) - throw new ObjectDisposedException($"{this}"); + ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this); if (CheckAccess() == true) { action(); return; } + var task = _factory.StartNew(action, _cancellationToken); task.Wait(_cancellationToken); } + public TResult Invoke(Func func) + { + ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this); + + if (CheckAccess() == true) + { + return func(); + } + + var task = _factory.StartNew(func, _cancellationToken); + task.Wait(_cancellationToken); + return task.Result; + } + public void Post(Action action) { - if (_cancellationToken.IsCancellationRequested == true) - throw new ObjectDisposedException($"{this}"); + ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this); _factory.StartNew(action, _cancellationToken); } @@ -107,69 +107,25 @@ public async Task InvokeAsync(Task task) public Task InvokeAsync(Action action) { - if (_cancellationToken.IsCancellationRequested == true) - throw new ObjectDisposedException($"{this}"); + ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this); return _factory.StartNew(action, _cancellationToken); } - public TResult Invoke(Func func) - { - if (_cancellationTokenSource.IsCancellationRequested == true) - throw new ObjectDisposedException($"{this}"); - - if (CheckAccess() == true) - { - return func(); - } - var task = _factory.StartNew(func, _cancellationToken); - task.Wait(_cancellationToken); - return task.Result; - } - public async Task InvokeAsync(Func callback) { - if (_cancellationTokenSource.IsCancellationRequested == true) - throw new ObjectDisposedException($"{this}"); + ObjectDisposedException.ThrowIf(_cancellationToken.IsCancellationRequested, this); return await _factory.StartNew(callback, _cancellationToken); } public void Dispose() { - if (_isDisposed == true) - throw new ObjectDisposedException($"{this}"); - if (_cancellationTokenSource.IsCancellationRequested == true) - throw new ObjectDisposedException($"{this}"); + ObjectDisposedException.ThrowIf(_cancellationTokenSource.IsCancellationRequested, this); _cancellationTokenSource.Cancel(); _scheduler.WaitClose(); _cancellationTokenSource.Dispose(); - _isDisposed = true; GC.SuppressFinalize(this); } - -#if DEBUG - internal string StackTrace => $"{_stackTrace}"; -#endif -} - -public sealed class DispatcherSynchronizationContext : SynchronizationContext -{ - private readonly TaskFactory _factory; - - internal DispatcherSynchronizationContext(TaskFactory factory) - { - _factory = factory; - } - - public override void Send(SendOrPostCallback d, object? state) - { - _factory.StartNew(() => d(state)).Wait(); - } - - public override void Post(SendOrPostCallback d, object? state) - { - _factory.StartNew(() => d(state)); - } } diff --git a/src/JSSoft.Communication/Threading/DispatcherScheduler.cs b/src/JSSoft.Communication/Threading/DispatcherScheduler.cs index d74c7d2..b183224 100644 --- a/src/JSSoft.Communication/Threading/DispatcherScheduler.cs +++ b/src/JSSoft.Communication/Threading/DispatcherScheduler.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Collections.Concurrent; @@ -30,39 +13,23 @@ namespace JSSoft.Communication.Threading; public sealed class DispatcherScheduler : TaskScheduler { - private readonly Dispatcher _dispatcher; private readonly CancellationToken _cancellationToken; private readonly ConcurrentQueue _taskQueue = []; private readonly ManualResetEvent _executionEventSet = new(false); private bool _isRunning = true; private bool _isClosed; - internal DispatcherScheduler(Dispatcher dispatcher, CancellationToken cancellationToken) + internal DispatcherScheduler(CancellationToken cancellationToken) { - _dispatcher = dispatcher; _cancellationToken = cancellationToken; } - protected override IEnumerable GetScheduledTasks() => _taskQueue; - - protected override void QueueTask(Task task) - { - if (_cancellationToken.IsCancellationRequested != true) - { - _taskQueue.Enqueue(task); - _executionEventSet.Set(); - } - } - - protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) - { - return false; - } - internal void WaitClose() { if (_isClosed == true) + { throw new InvalidOperationException("Dispatcher is already closed."); + } while (_isRunning == true) { @@ -74,8 +41,10 @@ internal void WaitClose() { _executionEventSet.Close(); } + Thread.Sleep(1); } + _executionEventSet.Dispose(); _isClosed = true; } @@ -84,11 +53,6 @@ internal void Run() { try { -#if DEBUG - var owner = _dispatcher.Owner; - var stackTrace = _dispatcher.StackTrace; -#endif - while (_cancellationToken.IsCancellationRequested != true) { if (_taskQueue.TryDequeue(out var task) == true) @@ -108,12 +72,31 @@ internal void Run() } } } + if (_isClosed == true) + { throw new InvalidOperationException("Dispatcher is already closed."); + } } finally { _isRunning = false; } } + + protected override IEnumerable GetScheduledTasks() => _taskQueue; + + protected override void QueueTask(Task task) + { + if (_cancellationToken.IsCancellationRequested != true) + { + _taskQueue.Enqueue(task); + _executionEventSet.Set(); + } + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + return false; + } } diff --git a/src/JSSoft.Communication/Threading/DispatcherSynchronizationContext.cs b/src/JSSoft.Communication/Threading/DispatcherSynchronizationContext.cs new file mode 100644 index 0000000..92493c2 --- /dev/null +++ b/src/JSSoft.Communication/Threading/DispatcherSynchronizationContext.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace JSSoft.Communication.Threading; + +public sealed class DispatcherSynchronizationContext : SynchronizationContext +{ + private readonly TaskFactory _factory; + + internal DispatcherSynchronizationContext(TaskFactory factory) + { + _factory = factory; + } + + public override void Send(SendOrPostCallback d, object? state) + { + _factory.StartNew(() => d(state)).Wait(); + } + + public override void Post(SendOrPostCallback d, object? state) + { + _factory.StartNew(() => d(state)); + } +} diff --git a/src/JSSoft.Communication/UnreachableException.cs b/src/JSSoft.Communication/UnreachableException.cs index 0218444..5cb2e30 100644 --- a/src/JSSoft.Communication/UnreachableException.cs +++ b/src/JSSoft.Communication/UnreachableException.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// #if !NET7_0_OR_GREATER using System; diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Application.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Application.cs index 3e8f967..287764c 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Application.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Application.cs @@ -1,47 +1,35 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.Services; using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; -using System.Collections; +using System.IO; using System.Linq; -using System.Collections.Generic; -using JSSoft.Terminals; using System.Net; +using System.Threading; +using System.Threading.Tasks; +using JSSoft.Communication.Services; +using JSSoft.Terminals; namespace JSSoft.Communication.ConsoleApp; -sealed class Application : IApplication, IServiceProvider +internal sealed class Application : IApplication, IServiceProvider { - private static readonly string postfix = TerminalEnvironment.IsWindows() == true ? ">" : "$"; + private static readonly string Postfix = TerminalEnvironment.IsWindows() == true ? ">" : "$"; private readonly ApplicationOptions _option; private readonly IServiceContext _serviceContext; - private readonly INotifyUserService _userServiceNotification; private readonly CompositionContainer _container; +#if SERVER + private readonly bool _isServer = true; +#else + private readonly bool _isServer = false; +#endif + private bool _isDisposed; private CancellationTokenSource? _cancellationTokenSource; private string title = string.Empty; @@ -51,12 +39,6 @@ static Application() Logging.LogUtility.Logger = Logging.TraceLogger.Default; } -#if SERVER - private readonly bool _isServer = true; -#else - private readonly bool _isServer = false; -#endif - public Application(ApplicationOptions option) { _container = new CompositionContainer(new AssemblyCatalog(typeof(Application).Assembly)); @@ -68,11 +50,14 @@ public Application(ApplicationOptions option) _serviceContext = _container.GetExportedValue(); _serviceContext.Opened += ServiceContext_Opened; _serviceContext.Closed += ServiceContext_Closed; - _userServiceNotification = _container.GetExportedValue(); - _userServiceNotification.LoggedIn += UserServiceNotification_LoggedIn; - _userServiceNotification.LoggedOut += UserServiceNotification_LoggedOut; - _userServiceNotification.MessageReceived += UserServiceNotification_MessageReceived; Title = "Server"; + if (_container.GetExportedValue() is { } userServiceNotification) + { + userServiceNotification = _container.GetExportedValue(); + userServiceNotification.LoggedIn += UserServiceNotification_LoggedIn; + userServiceNotification.LoggedOut += UserServiceNotification_LoggedOut; + userServiceNotification.MessageReceived += UserServiceNotification_MessageReceived; + } } public bool IsOpened { get; private set; } @@ -87,6 +72,16 @@ public string Title } } + internal Guid Token { get; set; } + + internal Guid UserToken { get; private set; } + + internal string UserId { get; private set; } = string.Empty; + + private TextWriter Out => Console.Out; + + private SystemTerminal Terminal => _container.GetExportedValue(); + public void Dispose() { if (_isDisposed != true) @@ -96,44 +91,115 @@ public void Dispose() } } - internal void Login(string userID, Guid token) + public async Task StartAsync() { - UserID = userID; - UserToken = token; + if (_cancellationTokenSource != null) + { + throw new InvalidOperationException("Application is already started."); + } + + _cancellationTokenSource = new CancellationTokenSource(); + _serviceContext.EndPoint = new DnsEndPoint(_option.Host, _option.Port); + try + { + Token = await _serviceContext.OpenAsync(_cancellationTokenSource.Token); + } + catch + { + await _serviceContext.AbortAsync(); + } + UpdatePrompt(); - Out.WriteLine("사용자 관련 명령을 수행하려면 'help user' 을(를) 입력하세요."); + await Terminal.StartAsync(_cancellationTokenSource.Token); } - internal void Logout() + public async Task StopAsync(int exitCode) { - UserID = string.Empty; - UserToken = Guid.Empty; - UpdatePrompt(); + if (_cancellationTokenSource == null) + { + throw new InvalidOperationException("Application is not started."); + } + + await _cancellationTokenSource.CancelAsync(); + if (_serviceContext.ServiceState == ServiceState.Open) + { + _serviceContext.Closed -= ServiceContext_Closed; + try + { + await _serviceContext.CloseAsync(Token, cancellationToken: default); + } + catch + { + await _serviceContext.AbortAsync(); + } + } + + _cancellationTokenSource.Dispose(); + _cancellationTokenSource = null; } - internal Guid Token { get; set; } + object? IServiceProvider.GetService(Type serviceType) + { + if (serviceType == typeof(IServiceProvider)) + { + return this; + } - internal Guid UserToken { get; private set; } + if (typeof(IEnumerable).IsAssignableFrom(serviceType) + && serviceType.GenericTypeArguments.Length == 1) + { + var itemType = serviceType.GenericTypeArguments.First(); + var contractName = AttributedModelServices.GetContractName(itemType); + var items = _container.GetExportedValues(contractName); + var listGenericType = typeof(List<>); + var list = listGenericType.MakeGenericType(itemType); + var ci = list.GetConstructor([typeof(int)])!; + var instance = (IList)ci.Invoke([items.Count(),])!; + foreach (var item in items) + { + instance.Add(item); + } - internal string UserID { get; private set; } = string.Empty; + return instance; + } + else + { + var contractName = AttributedModelServices.GetContractName(serviceType); + return _container.GetExportedValue(contractName); + } + } - private TextWriter Out => Console.Out; + internal void Login(string userId, Guid token) + { + UserId = userId; + UserToken = token; + UpdatePrompt(); + Out.WriteLine("사용자 관련 명령을 수행하려면 'help user' 을(를) 입력하세요."); + } - private SystemTerminal Terminal => _container.GetExportedValue(); + internal void Logout() + { + UserId = string.Empty; + UserToken = Guid.Empty; + UpdatePrompt(); + } private void UpdatePrompt() { var prompt = string.Empty; var isOpened = IsOpened; - var userID = UserID; + var userId = UserId; if (isOpened == true) { prompt = EndPointUtility.ToString(_serviceContext.EndPoint); - if (userID != string.Empty) - prompt += $"@{userID}"; + if (userId != string.Empty) + { + prompt += $"@{userId}"; + } } - Terminal.Prompt = $"{prompt} {postfix} "; + + Terminal.Prompt = $"{prompt} {Postfix} "; } private void ServiceContext_Opened(object? sender, EventArgs e) @@ -151,6 +217,7 @@ private void ServiceContext_Opened(object? sender, EventArgs e) Title = $"Client {EndPointUtility.ToString(_serviceContext.EndPoint)}"; Out.WriteLine("서버에 연결되었습니다."); } + Out.WriteLine("사용 가능한 명령을 확인려면 '--help' 을(를) 입력하세요."); Out.WriteLine("로그인을 하려면 'login admin admin' 을(를) 입력하세요."); } @@ -175,100 +242,27 @@ private void ServiceContext_Closed(object? sender, EventArgs e) private void UserServiceNotification_LoggedIn(object? sender, UserEventArgs e) { - Out.WriteLine($"User logged in: {e.UserID}"); + Out.WriteLine($"User logged in: {e.UserId}"); } private void UserServiceNotification_LoggedOut(object? sender, UserEventArgs e) { - Out.WriteLine($"User logged out: {e.UserID}"); + Out.WriteLine($"User logged out: {e.UserId}"); } private void UserServiceNotification_MessageReceived(object? sender, UserMessageEventArgs e) { - if (e.Sender == UserID) + if (e.Sender == UserId) { - var text = TerminalStringBuilder.GetString($"'{e.Receiver}'에게 귓속말: {e.Message}", TerminalColorType.BrightMagenta); + var message = $"to '{e.Receiver}'에게 귓속말: {e.Message}"; + var text = TerminalStringBuilder.GetString(message, TerminalColorType.BrightMagenta); Out.WriteLine(text); } - else if (e.Receiver == UserID) + else if (e.Receiver == UserId) { - var text = TerminalStringBuilder.GetString($"'{e.Receiver}'의 귓속말: {e.Message}", TerminalColorType.BrightMagenta); + var message = $"from '{e.Receiver}': {e.Message}"; + var text = TerminalStringBuilder.GetString(message, TerminalColorType.BrightMagenta); Out.WriteLine(text); } } - - #region IServiceProvider - - object? IServiceProvider.GetService(Type serviceType) - { - if (serviceType == typeof(IServiceProvider)) - return this; - - if (typeof(IEnumerable).IsAssignableFrom(serviceType) && serviceType.GenericTypeArguments.Length == 1) - { - var itemType = serviceType.GenericTypeArguments.First(); - var contractName = AttributedModelServices.GetContractName(itemType); - var items = _container.GetExportedValues(contractName); - var listGenericType = typeof(List<>); - var list = listGenericType.MakeGenericType(itemType); - var ci = list.GetConstructor(new Type[] { typeof(int) })!; - var instance = (IList)ci.Invoke(new object[] { items.Count(), })!; - foreach (var item in items) - { - instance.Add(item); - } - return instance; - } - else - { - var contractName = AttributedModelServices.GetContractName(serviceType); - return _container.GetExportedValue(contractName); - } - } - - #endregion - - #region IApplication - - public async Task StartAsync() - { - if (_cancellationTokenSource != null) - throw new InvalidOperationException("Application is already started."); - - _cancellationTokenSource = new CancellationTokenSource(); - _serviceContext.EndPoint = new DnsEndPoint(_option.Host, _option.Port); - try - { - Token = await _serviceContext.OpenAsync(_cancellationTokenSource.Token); - } - catch (Exception e) - { - var text = TerminalStringBuilder.GetString(e.Message, TerminalColorType.BrightRed); - await _serviceContext.AbortAsync(); - } - UpdatePrompt(); - await Terminal.StartAsync(_cancellationTokenSource.Token); - } - - public async Task StopAsync(int exitCode) - { - if (_cancellationTokenSource == null) - throw new InvalidOperationException("Application is not started."); - - _cancellationTokenSource.Cancel(); - if (_serviceContext.ServiceState == ServiceState.Open) - { - _serviceContext.Closed -= ServiceContext_Closed; - try - { - await _serviceContext.CloseAsync(Token, cancellationToken: default); - } - catch - { - await _serviceContext.AbortAsync(); - } - } - } - - #endregion -} \ No newline at end of file +} diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/ApplicationOptions.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/ApplicationOptions.cs index 6fee939..c768575 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/ApplicationOptions.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/ApplicationOptions.cs @@ -1,27 +1,10 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Commands; using System.ComponentModel.Composition; +using JSSoft.Commands; namespace JSSoft.Communication.ConsoleApp; @@ -48,4 +31,4 @@ public static ApplicationOptions Parse(string[] args) parser.Parse(args); return options; } -} \ No newline at end of file +} diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/CommandContext.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/CommandContext.cs index 7c42765..777793e 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/CommandContext.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/CommandContext.cs @@ -1,36 +1,23 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// +using System.Collections.Generic; +using System.ComponentModel.Composition; using JSSoft.Commands; using JSSoft.Communication.Commands; using JSSoft.Terminals; -using System.Collections.Generic; -using System.ComponentModel.Composition; namespace JSSoft.Communication.ConsoleApp; [Export(typeof(CommandContext))] [method: ImportingConstructor] -sealed class CommandContext([ImportMany] IEnumerable commands, HelpCommand helpCommand, VersionCommand versionCommand) : CommandContextBase(commands) +internal sealed class CommandContext( + [ImportMany] IEnumerable commands, + HelpCommand helpCommand, + VersionCommand versionCommand) + : CommandContextBase(commands) { protected override ICommand HelpCommand { get; } = helpCommand; @@ -48,4 +35,4 @@ protected override void OnEmptyExecute() tsb.AppendEnd(); Out.Write(tsb.ToString()); } -} \ No newline at end of file +} diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/CloseCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/CloseCommand.cs index da830f8..8a54fa9 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/CloseCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/CloseCommand.cs @@ -1,44 +1,29 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.ConsoleApp; -using JSSoft.Commands; using System; -using System.Threading.Tasks; -using System.Threading; using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using JSSoft.Commands; +using JSSoft.Communication.ConsoleApp; namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [method: ImportingConstructor] -sealed class CloseCommand(IServiceContext serviceContext, Application application) : CommandAsyncBase +internal sealed class CloseCommand(IServiceContext serviceContext, Application application) + : CommandAsyncBase { private readonly IServiceContext _serviceContext = serviceContext; private readonly Application _application = application; public override bool IsEnabled => _serviceContext.ServiceState == ServiceState.Open; - protected override async Task OnExecuteAsync(CancellationToken cancellationToken, IProgress progress) + protected override async Task OnExecuteAsync( + CancellationToken cancellationToken, IProgress progress) { try { diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/DataCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/DataCommand.cs index 33e4062..ffc8f02 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/DataCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/DataCommand.cs @@ -1,53 +1,35 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.ConsoleApp; -using JSSoft.Communication.Services; -using JSSoft.Commands; using System; -using System.Threading.Tasks; using System.ComponentModel.Composition; using System.Threading; +using System.Threading.Tasks; +using JSSoft.Commands; +using JSSoft.Communication.ConsoleApp; +using JSSoft.Communication.Services; namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [method: ImportingConstructor] -class DataCommand(Application application, IDataService dataService) : CommandMethodBase +internal class DataCommand(Application application, IDataService dataService) : CommandMethodBase { private readonly Application _application = application; private readonly IDataService _dataService = dataService; + public override bool IsEnabled => _application.UserToken != Guid.Empty; + [CommandMethod] public Task CreateAsync(string dataBaseName, CancellationToken cancellationToken) { return _dataService.CreateDataBaseAsync(dataBaseName, cancellationToken); } - public override bool IsEnabled => _application.UserToken != Guid.Empty; - - protected override bool IsMethodEnabled(CommandMethodDescriptor descriptor) + protected override bool IsMethodEnabled(CommandMethodDescriptor memberDescriptor) { return _application.UserToken != Guid.Empty; } - } diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/ExitCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/ExitCommand.cs index d785006..06d14ad 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/ExitCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/ExitCommand.cs @@ -1,44 +1,28 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.ConsoleApp; -using JSSoft.Commands; using System; -using System.Threading.Tasks; -using System.Threading; using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using JSSoft.Commands; +using JSSoft.Communication.ConsoleApp; namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [method: ImportingConstructor] -sealed class ExitCommand(IApplication application) : CommandAsyncBase +internal sealed class ExitCommand(IApplication application) : CommandAsyncBase { private readonly IApplication _application = application; [CommandPropertyRequired(DefaultValue = 0)] public int ExitCode { get; set; } - protected override Task OnExecuteAsync(CancellationToken cancellationToken, IProgress progress) + protected override Task OnExecuteAsync( + CancellationToken cancellationToken, IProgress progress) { return _application.StopAsync(ExitCode); } diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/HelpCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/HelpCommand.cs index 7831972..62559b9 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/HelpCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/HelpCommand.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + using System.ComponentModel.Composition; using JSSoft.Commands; @@ -5,6 +10,6 @@ namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [Export(typeof(HelpCommand))] -sealed class HelpCommand : HelpCommandBase +internal sealed class HelpCommand : HelpCommandBase { } diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/LoginCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/LoginCommand.cs index 753979d..174eef3 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/LoginCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/LoginCommand.cs @@ -1,53 +1,43 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.ConsoleApp; -using System.Threading.Tasks; using System; +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; using JSSoft.Commands; +using JSSoft.Communication.ConsoleApp; using JSSoft.Communication.Services; -using System.Threading; -using System.ComponentModel.Composition; namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [method: ImportingConstructor] -sealed class LoginCommand(Application application, IUserService userService) : CommandAsyncBase +internal sealed class LoginCommand(Application application, IUserService userService) + : CommandAsyncBase { private readonly Application _application = application; private readonly IUserService _userService = userService; [CommandPropertyRequired] - public string UserID { get; set; } = string.Empty; + public string UserId { get; set; } = string.Empty; [CommandPropertyRequired] public string Password { get; set; } = string.Empty; public override bool IsEnabled => _application.UserToken == Guid.Empty; - protected override async Task OnExecuteAsync(CancellationToken cancellationToken, IProgress progress) + protected override async Task OnExecuteAsync( + CancellationToken cancellationToken, IProgress progress) { - var token = await _userService.LoginAsync(UserID, Password, cancellationToken); - _application.Login(UserID, token); + var options = new UserLoginOptions() + { + UserId = UserId, + Password = Password, + }; + var token = await _userService.LoginAsync(options, cancellationToken); + _application.Login(UserId, token); } } diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/LogoutCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/LogoutCommand.cs index 010253c..0c633c8 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/LogoutCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/LogoutCommand.cs @@ -1,45 +1,30 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.ConsoleApp; -using JSSoft.Communication.Services; -using JSSoft.Commands; using System; using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; +using JSSoft.Commands; +using JSSoft.Communication.ConsoleApp; +using JSSoft.Communication.Services; namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [method: ImportingConstructor] -sealed class LogoutCommand(Application application, IUserService userService) : CommandAsyncBase +internal sealed class LogoutCommand(Application application, IUserService userService) + : CommandAsyncBase { private readonly Application _application = application; private readonly IUserService _userService = userService; public override bool IsEnabled => _application.UserToken != Guid.Empty; - protected override async Task OnExecuteAsync(CancellationToken cancellationToken, IProgress progress) + protected override async Task OnExecuteAsync( + CancellationToken cancellationToken, IProgress progress) { await _userService.LogoutAsync(_application.UserToken, cancellationToken); _application.Logout(); diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/OpenCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/OpenCommand.cs index 20dc931..4d7602b 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/OpenCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/OpenCommand.cs @@ -1,44 +1,29 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.ConsoleApp; -using JSSoft.Commands; using System; using System.ComponentModel.Composition; using System.Threading; using System.Threading.Tasks; +using JSSoft.Commands; +using JSSoft.Communication.ConsoleApp; namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [method: ImportingConstructor] -sealed class OpenCommand(IServiceContext serviceContext, Application application) : CommandAsyncBase +internal sealed class OpenCommand(IServiceContext serviceContext, Application application) + : CommandAsyncBase { private readonly IServiceContext _serviceContext = serviceContext; private readonly Application _application = application; public override bool IsEnabled => _serviceContext.ServiceState == ServiceState.None; - protected override async Task OnExecuteAsync(CancellationToken cancellationToken, IProgress progress) + protected override async Task OnExecuteAsync( + CancellationToken cancellationToken, IProgress progress) { try { diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/UserCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/UserCommand.cs index f3723e1..7f156a8 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/UserCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/UserCommand.cs @@ -1,93 +1,112 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Communication.ConsoleApp; -using JSSoft.Communication.Services; -using JSSoft.Commands; using System; -using System.Threading.Tasks; using System.ComponentModel.Composition; +using System.Text; using System.Threading; +using System.Threading.Tasks; +using JSSoft.Commands; +using JSSoft.Communication.ConsoleApp; +using JSSoft.Communication.Services; namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [method: ImportingConstructor] -class UserCommand(Application application, IUserService userService) : CommandMethodBase +internal class UserCommand(Application application, IUserService userService) : CommandMethodBase { private readonly Application _application = application; private readonly IUserService _userService = userService; + public override bool IsEnabled => _application.UserToken != Guid.Empty; + [CommandMethod] - public Task CreateAsync(string userID, string password, Authority authority, CancellationToken cancellationToken) + public Task CreateAsync( + string userId, string password, Authority authority, CancellationToken cancellationToken) { - return _userService.CreateAsync(_application.UserToken, userID, password, authority, cancellationToken); + var options = new UserCreateOptions + { + Token = _application.UserToken, + UserId = userId, + Password = password, + Authority = authority, + }; + return _userService.CreateAsync(options, cancellationToken); } [CommandMethod] - public Task DeleteAsync(string userID, CancellationToken cancellationToken) + public Task DeleteAsync(string userId, CancellationToken cancellationToken) { - return _userService.DeleteAsync(_application.UserToken, userID, cancellationToken); + var options = new UserDeleteOptions + { + Token = _application.UserToken, + UserId = userId, + }; + return _userService.DeleteAsync(options, cancellationToken); } [CommandMethod] public Task RenameAsync(string userName, CancellationToken cancellationToken) { - return _userService.RenameAsync(_application.UserToken, userName, cancellationToken); + var options = new UserRenameOptions + { + Token = _application.UserToken, + UserName = userName, + }; + return _userService.RenameAsync(options, cancellationToken); } [CommandMethod] - public Task AuthorityAsync(string userID, Authority authority, CancellationToken cancellationToken) + public Task AuthorityAsync( + string userId, Authority authority, CancellationToken cancellationToken) { - return _userService.SetAuthorityAsync(_application.UserToken, userID, authority, cancellationToken); + var options = new UserAuthorityOptions + { + Token = _application.UserToken, + UserId = userId, + Authority = authority, + }; + return _userService.SetAuthorityAsync(options, cancellationToken); } [CommandMethod] - public async Task InfoAsync(string userID, CancellationToken cancellationToken) + public async Task InfoAsync(string userId, CancellationToken cancellationToken) { - var (userName, authority) = await _userService.GetInfoAsync(_application.UserToken, userID, cancellationToken); - Out.WriteLine($"UseName: {userName}"); - Out.WriteLine($"Authority: {authority}"); + var token = _application.UserToken; + var userInfo = await _userService.GetInfoAsync(token, userId, cancellationToken); + await Out.WriteLineAsync($"UseName: {userInfo.UserName}"); + await Out.WriteLineAsync($"Authority: {userInfo.Authority}"); } [CommandMethod] public async Task ListAsync(CancellationToken cancellationToken) { var items = await _userService.GetUsersAsync(_application.UserToken, cancellationToken); + var sb = new StringBuilder(); foreach (var item in items) { - Out.WriteLine(item); + sb.AppendLine(item); } + + await Out.WriteLineAsync(sb.ToString()); } [CommandMethod] - public Task SendMessageAsync(string userID, string message, CancellationToken cancellationToken) + public Task SendMessageAsync(string userId, string message, CancellationToken cancellationToken) { - return _userService.SendMessageAsync(_application.UserToken, userID, message, cancellationToken); + var options = new UserSendMessageOptions + { + Token = _application.UserToken, + UserId = userId, + Message = message, + }; + return _userService.SendMessageAsync(options, cancellationToken); } - public override bool IsEnabled => _application.UserToken != Guid.Empty; - - protected override bool IsMethodEnabled(CommandMethodDescriptor descriptor) + protected override bool IsMethodEnabled(CommandMethodDescriptor memberDescriptor) { return _application.UserToken != Guid.Empty; } diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/VersionCommand.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/VersionCommand.cs index adb5e4e..8a0f474 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/VersionCommand.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/Commands/VersionCommand.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + using System.ComponentModel.Composition; using JSSoft.Commands; @@ -5,6 +10,6 @@ namespace JSSoft.Communication.Commands; [Export(typeof(ICommand))] [Export(typeof(VersionCommand))] -sealed class VersionCommand : VersionCommandBase +internal sealed class VersionCommand : VersionCommandBase { } diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/IApplication.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/IApplication.cs index f806d3c..337a1a9 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/IApplication.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/IApplication.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Threading.Tasks; @@ -27,9 +10,9 @@ namespace JSSoft.Communication.ConsoleApp; public interface IApplication : IDisposable { + string Title { get; set; } + Task StartAsync(); Task StopAsync(int exitCode); - - string Title { get; set; } -} \ No newline at end of file +} diff --git a/src/Sharing/JSSoft.Communication.ConsoleApp/SystemTerminal.cs b/src/Sharing/JSSoft.Communication.ConsoleApp/SystemTerminal.cs index d7d26e3..21baf9c 100644 --- a/src/Sharing/JSSoft.Communication.ConsoleApp/SystemTerminal.cs +++ b/src/Sharing/JSSoft.Communication.ConsoleApp/SystemTerminal.cs @@ -1,41 +1,24 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -using JSSoft.Commands.Extensions; +using System.ComponentModel.Composition; using System.IO; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.ComponentModel.Composition; +using JSSoft.Commands.Extensions; using JSSoft.Terminals; namespace JSSoft.Communication.ConsoleApp; [Export] [method: ImportingConstructor] -sealed class SystemTerminal(Application application, CommandContext commandContext) +internal sealed class SystemTerminal(Application application, CommandContext commandContext) : SystemTerminalBase { - private static readonly string postfix = TerminalEnvironment.IsWindows() == true ? "> " : "$ "; + private static readonly string Postfix = TerminalEnvironment.IsWindows() == true ? "> " : "$ "; private readonly Application _application = application; private readonly CommandContext _commandContext = commandContext; @@ -52,20 +35,20 @@ protected override void OnInitialize(TextWriter @out, TextWriter error) protected override string FormatPrompt(string prompt) { - if (_application.UserID == string.Empty) + if (_application.UserId == string.Empty) { return prompt; } else { var tb = new TerminalStringBuilder(); - var pattern = $"(.+@)(.+).{{{postfix.Length}}}"; + var pattern = $"(.+@)(.+).{{{Postfix.Length}}}"; var match = Regex.Match(prompt, pattern); tb.Append(match.Groups[1].Value); tb.Foreground = TerminalColorType.BrightGreen; tb.Append(match.Groups[2].Value); tb.Foreground = null; - tb.Append(postfix); + tb.Append(Postfix); return tb.ToString(); } } diff --git a/src/Sharing/JSSoft.Communication.Services/JSSoft.Communication.Services.projitems b/src/Sharing/JSSoft.Communication.Services/JSSoft.Communication.Services.projitems index 26b5d18..1ddfb5c 100644 --- a/src/Sharing/JSSoft.Communication.Services/JSSoft.Communication.Services.projitems +++ b/src/Sharing/JSSoft.Communication.Services/JSSoft.Communication.Services.projitems @@ -4,14 +4,6 @@ JSSoft.Communication.Services - - - - - - - - - + \ No newline at end of file diff --git a/src/Sharing/JSSoft.Communication.Services/Services/Authority.cs b/src/Sharing/JSSoft.Communication.Services/Services/Authority.cs index f1e60c8..bac7469 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/Authority.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/Authority.cs @@ -1,32 +1,24 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Services; public enum Authority { + /// + /// Administrator + /// Admin, + /// + /// Member + /// Member, - Guest -} \ No newline at end of file + /// + /// Guest + /// + Guest, +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/IDataService.cs b/src/Sharing/JSSoft.Communication.Services/Services/IDataService.cs index 1d80849..7c2bb38 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/IDataService.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/IDataService.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Threading; diff --git a/src/Sharing/JSSoft.Communication.Services/Services/INotifyUserService.cs b/src/Sharing/JSSoft.Communication.Services/Services/INotifyUserService.cs index f3ff7cf..31d6849 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/INotifyUserService.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/INotifyUserService.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; @@ -39,4 +22,4 @@ public interface INotifyUserService event EventHandler? Renamed; event EventHandler? AuthorityChanged; -} \ No newline at end of file +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/IUserCallback.cs b/src/Sharing/JSSoft.Communication.Services/Services/IUserCallback.cs index 8993950..5460aee 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/IUserCallback.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/IUserCallback.cs @@ -1,40 +1,23 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Services; public interface IUserCallback { - void OnCreated(string userID); + void OnCreated(string userId); - void OnDeleted(string userID); + void OnDeleted(string userId); - void OnLoggedIn(string userID); + void OnLoggedIn(string userId); - void OnLoggedOut(string userID); + void OnLoggedOut(string userId); void OnMessageReceived(string sender, string receiver, string message); - void OnRenamed(string userID, string userName); + void OnRenamed(string userId, string userName); - void OnAuthorityChanged(string userID, Authority authority); + void OnAuthorityChanged(string userId, Authority authority); } diff --git a/src/Sharing/JSSoft.Communication.Services/Services/IUserService.cs b/src/Sharing/JSSoft.Communication.Services/Services/IUserService.cs index 9bb596f..7efdf3f 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/IUserService.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/IUserService.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; using System.Threading; @@ -29,23 +12,23 @@ namespace JSSoft.Communication.Services; [ServiceContract] public interface IUserService { - Task CreateAsync(Guid token, string userID, string password, Authority authority, CancellationToken cancellationToken); + Task CreateAsync(UserCreateOptions options, CancellationToken cancellationToken); - Task DeleteAsync(Guid token, string userID, CancellationToken cancellationToken); + Task DeleteAsync(UserDeleteOptions options, CancellationToken cancellationToken); - Task RenameAsync(Guid token, string userName, CancellationToken cancellationToken); + Task RenameAsync(UserRenameOptions options, CancellationToken cancellationToken); - Task SetAuthorityAsync(Guid token, string userID, Authority authority, CancellationToken cancellationToken); + Task SetAuthorityAsync(UserAuthorityOptions options, CancellationToken cancellationToken); - Task LoginAsync(string userID, string password, CancellationToken cancellationToken); + Task LoginAsync(UserLoginOptions options, CancellationToken cancellationToken); Task LogoutAsync(Guid token, CancellationToken cancellationToken); - Task<(string userName, Authority authority)> GetInfoAsync(Guid token, string userID, CancellationToken cancellationToken); + Task GetInfoAsync(Guid token, string userId, CancellationToken cancellationToken); Task GetUsersAsync(Guid token, CancellationToken cancellationToken); - Task IsOnlineAsync(Guid token, string userID, CancellationToken cancellationToken); + Task IsOnlineAsync(Guid token, string userId, CancellationToken cancellationToken); - Task SendMessageAsync(Guid token, string userID, string message, CancellationToken cancellationToken); + Task SendMessageAsync(UserSendMessageOptions options, CancellationToken cancellationToken); } diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserAuthorityEventArgs.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserAuthorityEventArgs.cs index 52c6359..f30e4dd 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/UserAuthorityEventArgs.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserAuthorityEventArgs.cs @@ -1,28 +1,11 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Services; -public class UserAuthorityEventArgs(string userID, Authority authority) : UserEventArgs(userID) +public class UserAuthorityEventArgs(string userId, Authority authority) : UserEventArgs(userId) { public Authority Authority { get; } = authority; -} \ No newline at end of file +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserAuthorityOptions.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserAuthorityOptions.cs new file mode 100644 index 0000000..c30c853 --- /dev/null +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserAuthorityOptions.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; + +namespace JSSoft.Communication.Services; + +public sealed record class UserAuthorityOptions +{ + public required Guid Token { get; init; } + + public required string UserId { get; init; } + + public Authority Authority { get; init; } +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserCreateOptions.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserCreateOptions.cs new file mode 100644 index 0000000..58d4bb4 --- /dev/null +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserCreateOptions.cs @@ -0,0 +1,19 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; + +namespace JSSoft.Communication.Services; + +public sealed record class UserCreateOptions +{ + public required Guid Token { get; init; } + + public required string UserId { get; init; } + + public required string Password { get; init; } + + public Authority Authority { get; init; } +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserDeleteOptions.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserDeleteOptions.cs new file mode 100644 index 0000000..070a7df --- /dev/null +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserDeleteOptions.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; + +namespace JSSoft.Communication.Services; + +public sealed record class UserDeleteOptions +{ + public required Guid Token { get; init; } + + public required string UserId { get; init; } +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserEventArgs.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserEventArgs.cs index c5df313..346ca7c 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/UserEventArgs.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserEventArgs.cs @@ -1,30 +1,13 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; namespace JSSoft.Communication.Services; -public class UserEventArgs(string userID) : EventArgs +public class UserEventArgs(string userId) : EventArgs { - public string UserID { get; } = userID; -} \ No newline at end of file + public string UserId { get; } = userId; +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserInfo.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserInfo.cs new file mode 100644 index 0000000..060afe3 --- /dev/null +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserInfo.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; + +namespace JSSoft.Communication.Services; + +public readonly record struct UserInfo +{ + public string UserName { get; init; } + + public Authority Authority { get; init; } +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserLoginOptions.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserLoginOptions.cs new file mode 100644 index 0000000..07fb7c7 --- /dev/null +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserLoginOptions.cs @@ -0,0 +1,13 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +namespace JSSoft.Communication.Services; + +public sealed record class UserLoginOptions +{ + public required string UserId { get; init; } + + public required string Password { get; init; } +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserMessageEventArgs.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserMessageEventArgs.cs index e82823d..71bb551 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/UserMessageEventArgs.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserMessageEventArgs.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System; diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserNameEventArgs.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserNameEventArgs.cs index 3ddc7b0..0ea6088 100644 --- a/src/Sharing/JSSoft.Communication.Services/Services/UserNameEventArgs.cs +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserNameEventArgs.cs @@ -1,28 +1,11 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Services; -public class UserNameEventArgs(string userID, string userName) : UserEventArgs(userID) +public class UserNameEventArgs(string userId, string userName) : UserEventArgs(userId) { public string UserName { get; } = userName; -} \ No newline at end of file +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserRenameOptions.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserRenameOptions.cs new file mode 100644 index 0000000..8c207ff --- /dev/null +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserRenameOptions.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; + +namespace JSSoft.Communication.Services; + +public sealed record class UserRenameOptions +{ + public required Guid Token { get; init; } + + public required string UserName { get; init; } +} diff --git a/src/Sharing/JSSoft.Communication.Services/Services/UserSendMessageOptions.cs b/src/Sharing/JSSoft.Communication.Services/Services/UserSendMessageOptions.cs new file mode 100644 index 0000000..bec74c0 --- /dev/null +++ b/src/Sharing/JSSoft.Communication.Services/Services/UserSendMessageOptions.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +using System; + +namespace JSSoft.Communication.Services; + +public sealed record class UserSendMessageOptions +{ + public required Guid Token { get; init; } + + public required string UserId { get; init; } + + public string Message { get; init; } = string.Empty; +} diff --git a/stylecop.json b/stylecop.json new file mode 100644 index 0000000..e0bd48f --- /dev/null +++ b/stylecop.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace" + }, + "documentationRules": { + "companyName": "JSSoft", + "copyrightText": " Copyright (c) {year} {author}. All Rights Reserved.\n Licensed under the {licenseName} License. See {licenseFile} in the project root for license information.", + "variables": { + "licenseName": "MIT", + "licenseFile": "LICENSE.md", + "year": "2024", + "author": "Jeesu Choi" + } + } + } +} diff --git a/test/JSSoft.Communication.Tests/CallbackTest.cs b/test/JSSoft.Communication.Tests/CallbackTest.cs index 555ee38..eeb9e66 100644 --- a/test/JSSoft.Communication.Tests/CallbackTest.cs +++ b/test/JSSoft.Communication.Tests/CallbackTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using JSSoft.Communication.Tests.Extensions; using Xunit.Abstractions; @@ -47,18 +30,13 @@ public CallbackTest(ITestOutputHelper logger) logger.WriteLine($"{_endPoint}"); } - public class ValueEventArgs(object? value) : EventArgs - { - public object? Value { get; } = value; - } - public interface ITestService { void Invoke(); void Invoke(int value); - void Invoke((int value1, string value2) value); + void Invoke((int Value1, string Value2) value); } public interface ITestCallback @@ -67,41 +45,7 @@ public interface ITestCallback void OnInvoked(int value); - void OnInvoked((int value1, string value2) value); - } - - sealed class TestServer : ServerService, ITestService - { - public void Invoke() => Client.OnInvoked(); - - public void Invoke(int value) => Client.OnInvoked(value); - - public void Invoke((int value1, string value2) value) => Client.OnInvoked(value); - } - - sealed class TestClient : ClientService, ITestCallback - { - public AutoResetEvent AutoResetEvent { get; } = new(initialState: false); - - public event EventHandler? Invoked; - - void ITestCallback.OnInvoked() - { - Invoked?.Invoke(this, new(null)); - AutoResetEvent.Set(); - } - - void ITestCallback.OnInvoked(int value) - { - Invoked?.Invoke(this, new(value)); - AutoResetEvent.Set(); - } - - void ITestCallback.OnInvoked((int value1, string value2) value) - { - Invoked?.Invoke(this, new(value)); - AutoResetEvent.Set(); - } + void OnInvoked((int Value1, string Value2) value); } [Fact] @@ -165,4 +109,43 @@ public async Task DisposeAsync() _endPoint.Dispose(); _logger.WriteLine($"DisposeAsync 2"); } + + public class ValueEventArgs(object? value) : EventArgs + { + public object? Value { get; } = value; + } + + private sealed class TestServer : ServerService, ITestService + { + public void Invoke() => Client.OnInvoked(); + + public void Invoke(int value) => Client.OnInvoked(value); + + public void Invoke((int Value1, string Value2) value) => Client.OnInvoked(value); + } + + private sealed class TestClient : ClientService, ITestCallback + { + public event EventHandler? Invoked; + + public AutoResetEvent AutoResetEvent { get; } = new(initialState: false); + + void ITestCallback.OnInvoked() + { + Invoked?.Invoke(this, new(null)); + AutoResetEvent.Set(); + } + + void ITestCallback.OnInvoked(int value) + { + Invoked?.Invoke(this, new(value)); + AutoResetEvent.Set(); + } + + void ITestCallback.OnInvoked((int Value1, string Value2) value) + { + Invoked?.Invoke(this, new(value)); + AutoResetEvent.Set(); + } + } } diff --git a/test/JSSoft.Communication.Tests/ClientContextTest.cs b/test/JSSoft.Communication.Tests/ClientContextTest.cs index 348e5c1..158752d 100644 --- a/test/JSSoft.Communication.Tests/ClientContextTest.cs +++ b/test/JSSoft.Communication.Tests/ClientContextTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using JSSoft.Communication.Extensions; @@ -30,25 +13,17 @@ public sealed class ClientContextTest : IAsyncLifetime private readonly RandomEndPoint _endPoint = new(); private Guid _token; - public interface ITestService1 : IService - { - } - - public interface ITestService2 : IService - { - } - - sealed class TestService1 : ClientService + public ClientContextTest() { + _serverContext = new() { EndPoint = _endPoint }; } - sealed class TestService2 : ClientService + public interface ITestService1 : IService { } - public ClientContextTest() + public interface ITestService2 : IService { - _serverContext = new() { EndPoint = _endPoint }; } [Fact] @@ -56,16 +31,9 @@ public void Constructor_Test() { var clientContext0 = new ClientContext(); Assert.Empty(clientContext0.Services); - var clientContext1 = new ClientContext(services: - [ - new TestService2(), - ]); + var clientContext1 = new ClientContext(new TestService2()); Assert.Single(clientContext1.Services); - var clientContext2 = new ClientContext(services: - [ - new TestService1(), - new TestService2(), - ]); + var clientContext2 = new ClientContext(new TestService1(), new TestService2()); Assert.Equal(2, clientContext2.Services.Count); } @@ -131,7 +99,7 @@ public async Task Open_Cancel_Abort_TestAsync() { var endPoint = _serverContext.EndPoint; var clientContext = new ClientContext() { EndPoint = endPoint }; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); await Assert.ThrowsAnyAsync( () => clientContext.OpenAsync(cancellationTokenSource.Token)); Assert.Equal(ServiceState.Faulted, clientContext.ServiceState); @@ -157,7 +125,7 @@ public async Task Open_Close_Cancel_Abort_TestAsync() { var endPoint = _serverContext.EndPoint; var clientContext = new ClientContext() { EndPoint = endPoint }; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); var token = await clientContext.OpenAsync(cancellationToken: default); await Assert.ThrowsAnyAsync( () => clientContext.CloseAsync(token, cancellationTokenSource.Token)); @@ -185,7 +153,7 @@ public async Task Opened_TestAsync() var endPoint = _serverContext.EndPoint; var clientContext = new ClientContext() { EndPoint = endPoint }; var token = Guid.Empty; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 5000); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 5000); var result = await EventTestUtility.RaisesAsync( h => clientContext.Opened += h, h => clientContext.Opened -= h, @@ -200,7 +168,7 @@ public async Task Closed_TestAsync() { var endPoint = _serverContext.EndPoint; var clientContext = new ClientContext() { EndPoint = endPoint }; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 5000); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 5000); var token = await clientContext.OpenAsync(cancellationToken: default); var result = await EventTestUtility.RaisesAsync( h => clientContext.Closed += h, @@ -215,7 +183,7 @@ public async Task Faulted_TestAsync() { var endPoint = _serverContext.EndPoint; var clientContext = new ClientContext() { EndPoint = endPoint }; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); var result = await EventTestUtility.RaisesAsync( h => clientContext.Faulted += h, h => clientContext.Faulted -= h, @@ -227,6 +195,7 @@ public async Task Faulted_TestAsync() } catch { + // do nothing } }); @@ -250,6 +219,7 @@ public async Task Disconnected_TestAsync() } catch { + // do nothing } }); @@ -282,6 +252,15 @@ public async Task DisposeAsync() { await _serverContext.CloseAsync(_token, cancellationToken: default); } + _endPoint.Dispose(); } + + private sealed class TestService1 : ClientService + { + } + + private sealed class TestService2 : ClientService + { + } } diff --git a/test/JSSoft.Communication.Tests/ClientTestBase.cs b/test/JSSoft.Communication.Tests/ClientTestBase.cs index 76b72ca..e2c1529 100644 --- a/test/JSSoft.Communication.Tests/ClientTestBase.cs +++ b/test/JSSoft.Communication.Tests/ClientTestBase.cs @@ -1,24 +1,10 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + +// File may only contain a single type +#pragma warning disable SA1402 using JSSoft.Communication.Tests.Extensions; @@ -43,6 +29,10 @@ protected ClientTestBase(ServerService serverService) _clientContext = new(_clientService) { EndPoint = _endPoint }; } + protected TService Client => _client!; + + protected ServerService ServerService { get; } + public async Task InitializeAsync() { _serverToken = await _serverContext.OpenAsync(cancellationToken: default); @@ -56,10 +46,6 @@ public async Task DisposeAsync() await _clientContext.ReleaseAsync(_clientToken); _endPoint.Dispose(); } - - protected TService Client => _client!; - - protected ServerService ServerService { get; } } public abstract class ClientTestBase : IAsyncLifetime @@ -82,6 +68,10 @@ protected ClientTestBase(TServerSevice serverService) _clientContext = new(_clientService) { EndPoint = _endPoint }; } + protected TService Client => _client!; + + protected TServerSevice ServerService { get; } + public async Task InitializeAsync() { _serverToken = await _serverContext.OpenAsync(cancellationToken: default); @@ -95,8 +85,4 @@ public async Task DisposeAsync() await _clientContext.ReleaseAsync(_clientToken); _endPoint.Dispose(); } - - protected TService Client => _client!; - - protected TServerSevice ServerService { get; } } diff --git a/test/JSSoft.Communication.Tests/DispatcherTest.cs b/test/JSSoft.Communication.Tests/DispatcherTest.cs index b3125b7..9c8c391 100644 --- a/test/JSSoft.Communication.Tests/DispatcherTest.cs +++ b/test/JSSoft.Communication.Tests/DispatcherTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using JSSoft.Communication.Threading; @@ -85,17 +68,6 @@ await Assert.ThrowsAsync(async () => }); } - [Fact] - public async Task InvokeAsync_FailTest2() - { - var dispatcher = new Dispatcher(new()); - dispatcher.Dispose(); - await Assert.ThrowsAsync(async () => - { - await dispatcher.InvokeAsync(() => { }); - }); - } - [Fact] public async Task InvokeGenericAsync_Test() { @@ -115,20 +87,24 @@ await Assert.ThrowsAsync(async () => }); } + // "Thread.Sleep" should not be used in tests +#pragma warning disable S2925 [Fact] - public void InvokeGenericAsync_WaitTest() + public async Task InvokeGenericAsync_WaitTest() { var dispatcher = new Dispatcher(new()); var b = false; - dispatcher.InvokeAsync(() => + var task = dispatcher.InvokeAsync(() => { Thread.Sleep(1000); b = true; }); - Thread.Sleep(10); + await Task.Delay(10); dispatcher.Dispose(); + await task; Assert.True(b); } +#pragma warning restore S2925 [Fact] public void Post_Test() diff --git a/test/JSSoft.Communication.Tests/EventTestUtility.cs b/test/JSSoft.Communication.Tests/EventTestUtility.cs index 2034a28..9232995 100644 --- a/test/JSSoft.Communication.Tests/EventTestUtility.cs +++ b/test/JSSoft.Communication.Tests/EventTestUtility.cs @@ -1,33 +1,17 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests; public static class EventTestUtility { - public async static Task RaisesAsync(Action attach, Action detach, Func testCode) + public static async Task RaisesAsync( + Action attach, Action detach, Func testCode) { using var manualResetEvent = new ManualResetEvent(initialState: false); - attach(handler); + attach(Handler); try { await testCode(); @@ -38,10 +22,12 @@ public async static Task RaisesAsync(Action attach, Action +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; diff --git a/test/JSSoft.Communication.Tests/Exceptions/ArgumentNullExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/ArgumentNullExceptionTest.cs index b85b73a..b782a70 100644 --- a/test/JSSoft.Communication.Tests/Exceptions/ArgumentNullExceptionTest.cs +++ b/test/JSSoft.Communication.Tests/Exceptions/ArgumentNullExceptionTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; diff --git a/test/JSSoft.Communication.Tests/Exceptions/ArgumentOutOfRangeExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/ArgumentOutOfRangeExceptionTest.cs index 19f9a53..07ff1ff 100644 --- a/test/JSSoft.Communication.Tests/Exceptions/ArgumentOutOfRangeExceptionTest.cs +++ b/test/JSSoft.Communication.Tests/Exceptions/ArgumentOutOfRangeExceptionTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; diff --git a/test/JSSoft.Communication.Tests/Exceptions/ExceptionTestBase.cs b/test/JSSoft.Communication.Tests/Exceptions/ExceptionTestBase.cs index b369da7..2186dbe 100644 --- a/test/JSSoft.Communication.Tests/Exceptions/ExceptionTestBase.cs +++ b/test/JSSoft.Communication.Tests/Exceptions/ExceptionTestBase.cs @@ -1,28 +1,12 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; -public abstract class ExceptionTestBase : ClientTestBase.ITestService> +public abstract class ExceptionTestBase + : ClientTestBase.ITestService> where TException : Exception { protected ExceptionTestBase() @@ -32,21 +16,30 @@ protected ExceptionTestBase() public interface ITestService { - void Invoke() => throw (TException)Activator.CreateInstance(typeof(TException), args: [nameof(Invoke)])!; + void Invoke() + => throw (TException)Activator.CreateInstance(typeof(TException), nameof(Invoke))!; - Task InvokeAsync() => throw (TException)Activator.CreateInstance(typeof(TException), args: [nameof(Invoke)])!; + Task InvokeAsync() + => throw (TException)Activator.CreateInstance(typeof(TException), nameof(Invoke))!; - Task InvokeAndReturnAsync() => throw (TException)Activator.CreateInstance(typeof(TException), args: [nameof(Invoke)])!; - } - - sealed class TestServer : ServerService, ITestService - { + Task InvokeAndReturnAsync() + => throw (TException)Activator.CreateInstance(typeof(TException), nameof(Invoke))!; } [Fact] public void Invoke_Test() { - Client.Invoke(); + var b = true; + try + { + Client.Invoke(); + } + catch + { + b = false; + } + + Assert.True(b); } [Fact] @@ -72,4 +65,8 @@ public async Task InvokeAndReturnAsyncWithCancellation_Test() { await Assert.ThrowsAsync(() => Client.InvokeAndReturnAsync()); } + + private sealed class TestServer : ServerService, ITestService + { + } } diff --git a/test/JSSoft.Communication.Tests/Exceptions/IndexOutOfRangeExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/IndexOutOfRangeExceptionTest.cs index 3ee5230..b669af4 100644 --- a/test/JSSoft.Communication.Tests/Exceptions/IndexOutOfRangeExceptionTest.cs +++ b/test/JSSoft.Communication.Tests/Exceptions/IndexOutOfRangeExceptionTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; diff --git a/test/JSSoft.Communication.Tests/Exceptions/InvalidOperationExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/InvalidOperationExceptionTest.cs index 5c00c78..bfcb5bd 100644 --- a/test/JSSoft.Communication.Tests/Exceptions/InvalidOperationExceptionTest.cs +++ b/test/JSSoft.Communication.Tests/Exceptions/InvalidOperationExceptionTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; diff --git a/test/JSSoft.Communication.Tests/Exceptions/NotSupportedExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/NotSupportedExceptionTest.cs index 119211b..5a21e16 100644 --- a/test/JSSoft.Communication.Tests/Exceptions/NotSupportedExceptionTest.cs +++ b/test/JSSoft.Communication.Tests/Exceptions/NotSupportedExceptionTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; diff --git a/test/JSSoft.Communication.Tests/Exceptions/NullReferenceExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/NullReferenceExceptionTest.cs index 0ef9020..fd67257 100644 --- a/test/JSSoft.Communication.Tests/Exceptions/NullReferenceExceptionTest.cs +++ b/test/JSSoft.Communication.Tests/Exceptions/NullReferenceExceptionTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; diff --git a/test/JSSoft.Communication.Tests/Exceptions/ObjectDisposedExceptionTest.cs b/test/JSSoft.Communication.Tests/Exceptions/ObjectDisposedExceptionTest.cs index d0e4ca8..2438b41 100644 --- a/test/JSSoft.Communication.Tests/Exceptions/ObjectDisposedExceptionTest.cs +++ b/test/JSSoft.Communication.Tests/Exceptions/ObjectDisposedExceptionTest.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Exceptions; diff --git a/test/JSSoft.Communication.Tests/Extensions/ServiceContextExtensions.cs b/test/JSSoft.Communication.Tests/Extensions/ServiceContextExtensions.cs index 77c15e5..d00de6d 100644 --- a/test/JSSoft.Communication.Tests/Extensions/ServiceContextExtensions.cs +++ b/test/JSSoft.Communication.Tests/Extensions/ServiceContextExtensions.cs @@ -1,28 +1,11 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests.Extensions; -static class ServiceContextExtensions +internal static class ServiceContextExtensions { public static async Task ReleaseAsync(this IServiceContext @this, Guid token) { @@ -34,8 +17,10 @@ public static async Task ReleaseAsync(this IServiceContext @this, Guid token) } catch { + // do nothing } } + if (@this.ServiceState == ServiceState.Faulted) { await @this.AbortAsync(); diff --git a/test/JSSoft.Communication.Tests/GlobalUsings.cs b/test/JSSoft.Communication.Tests/GlobalUsings.cs index 3fab5bf..2ce8465 100644 --- a/test/JSSoft.Communication.Tests/GlobalUsings.cs +++ b/test/JSSoft.Communication.Tests/GlobalUsings.cs @@ -1,23 +1,6 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// -global using Xunit; \ No newline at end of file +global using Xunit; diff --git a/test/JSSoft.Communication.Tests/InvokeTest.cs b/test/JSSoft.Communication.Tests/InvokeTest.cs index 6b0b4d8..70a96ce 100644 --- a/test/JSSoft.Communication.Tests/InvokeTest.cs +++ b/test/JSSoft.Communication.Tests/InvokeTest.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + namespace JSSoft.Communication.Tests; public class InvokeTest : ClientTestBase @@ -13,45 +18,11 @@ public interface ITestService Task InvokeAsync(); - Task InvokeAndReturnAsync(); - Task InvokeAsync(CancellationToken cancellationToken); - Task InvokeAndReturnAsync(CancellationToken cancellationToken); - } - - public sealed class TestServer : ServerService, ITestService - { - public object? Result { get; set; } - - public void Invoke() - { - Result = nameof(Invoke); - } - - public Task InvokeAsync() - { - Result = nameof(InvokeAsync); - return Task.CompletedTask; - } - - public Task InvokeAndReturnAsync() - { - Result = nameof(InvokeAndReturnAsync); - return Task.Run(() => 2); - } - - public Task InvokeAsync(CancellationToken cancellationToken) - { - Result = nameof(InvokeAsync) + nameof(CancellationToken); - return Task.CompletedTask; - } + Task InvokeAndReturnAsync(); - public Task InvokeAndReturnAsync(CancellationToken cancellationToken) - { - Result = nameof(InvokeAndReturnAsync) + nameof(CancellationToken); - return Task.Run(() => 3); - } + Task InvokeAndReturnAsync(CancellationToken cancellationToken); } [Fact] @@ -87,7 +58,42 @@ public async Task InvokeAsyncWithCancellation_Test() public async Task InvokeAndReturnAsyncWithCancellation_Test() { var actualValue = await Client.InvokeAndReturnAsync(CancellationToken.None); - Assert.Equal(nameof(Client.InvokeAndReturnAsync) + nameof(CancellationToken), ServerService.Result); + var expectedResult = nameof(Client.InvokeAndReturnAsync) + nameof(CancellationToken); + Assert.Equal(expectedResult, ServerService.Result); Assert.Equal(3, actualValue); } + + public sealed class TestServer : ServerService, ITestService + { + public object? Result { get; set; } + + public void Invoke() + { + Result = nameof(Invoke); + } + + public Task InvokeAsync() + { + Result = nameof(InvokeAsync); + return Task.CompletedTask; + } + + public Task InvokeAsync(CancellationToken cancellationToken) + { + Result = nameof(InvokeAsync) + nameof(CancellationToken); + return Task.CompletedTask; + } + + public Task InvokeAndReturnAsync() + { + Result = nameof(InvokeAndReturnAsync); + return Task.Run(() => 2); + } + + public Task InvokeAndReturnAsync(CancellationToken cancellationToken) + { + Result = nameof(InvokeAndReturnAsync) + nameof(CancellationToken); + return Task.Run(() => 3); + } + } } diff --git a/test/JSSoft.Communication.Tests/RandomEndPoint.cs b/test/JSSoft.Communication.Tests/RandomEndPoint.cs index 7981fad..171a8bb 100644 --- a/test/JSSoft.Communication.Tests/RandomEndPoint.cs +++ b/test/JSSoft.Communication.Tests/RandomEndPoint.cs @@ -1,31 +1,14 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using System.Net; using System.Net.Sockets; namespace JSSoft.Communication.Tests; -sealed class RandomEndPoint : IDisposable +internal sealed class RandomEndPoint : IDisposable { private static readonly object LockObject = new(); private static readonly List PortList = []; @@ -50,15 +33,13 @@ public override string ToString() public void Dispose() { - if (_isDisposed == true) - { - throw new ObjectDisposedException($"{this}"); - } + ObjectDisposedException.ThrowIf(_isDisposed, this); lock (LockObject) { PortList.Remove(_endPoint.Port); } + _isDisposed = true; } diff --git a/test/JSSoft.Communication.Tests/ServerContextTest.cs b/test/JSSoft.Communication.Tests/ServerContextTest.cs index 3c859d1..32d017b 100644 --- a/test/JSSoft.Communication.Tests/ServerContextTest.cs +++ b/test/JSSoft.Communication.Tests/ServerContextTest.cs @@ -1,26 +1,8 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -using System.Security.Principal; +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// + using JSSoft.Communication.Extensions; namespace JSSoft.Communication.Tests; @@ -37,29 +19,14 @@ public interface ITestService2 : IService { } - sealed class TestService1 : ServerService, ITestService1 - { - } - - sealed class TestService2 : ServerService, ITestService2 - { - } - [Fact] public void Constructor_Test() { var serverContext0 = new ServerContext(); Assert.Empty(serverContext0.Services); - var serverContext1 = new ServerContext(services: - [ - new TestService2(), - ]); + var serverContext1 = new ServerContext(new TestService2()); Assert.Single(serverContext1.Services); - var serverContext2 = new ServerContext(services: - [ - new TestService1(), - new TestService2(), - ]); + var serverContext2 = new ServerContext(new TestService1(), new TestService2()); Assert.Equal(2, serverContext2.Services.Count); } @@ -125,7 +92,7 @@ public async Task Open_Cancel_Abort_TestAsync() { using var endPoint = new RandomEndPoint(); var serverContext = new ServerContext() { EndPoint = endPoint }; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); await Assert.ThrowsAnyAsync( () => serverContext.OpenAsync(cancellationTokenSource.Token)); Assert.Equal(ServiceState.Faulted, serverContext.ServiceState); @@ -149,7 +116,7 @@ public async Task Open_Close_Cancel_Abort_TestAsync() { using var endPoint = new RandomEndPoint(); var serverContext = new ServerContext() { EndPoint = endPoint }; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); var token = await serverContext.OpenAsync(cancellationToken: default); await Assert.ThrowsAnyAsync( () => serverContext.CloseAsync(token, cancellationTokenSource.Token)); @@ -178,7 +145,7 @@ public async Task Opened_TestAsync() using var endPoint = new RandomEndPoint(); var serverContext = new ServerContext() { EndPoint = endPoint }; var token = Guid.Empty; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: Timeout); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: Timeout); var result = await EventTestUtility.RaisesAsync( h => serverContext.Opened += h, h => serverContext.Opened -= h, @@ -193,7 +160,7 @@ public async Task Closed_TestAsync() { using var endPoint = new RandomEndPoint(); var serverContext = new ServerContext() { EndPoint = endPoint }; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: Timeout); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: Timeout); var token = await serverContext.OpenAsync(cancellationToken: default); var result = await EventTestUtility.RaisesAsync( h => serverContext.Closed += h, @@ -208,7 +175,7 @@ public async Task Faulted_TestAsync() { using var endPoint = new RandomEndPoint(); var serverContext = new ServerContext() { EndPoint = endPoint }; - var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); + using var cancellationTokenSource = new CancellationTokenSource(millisecondsDelay: 0); var result = await EventTestUtility.RaisesAsync( h => serverContext.Faulted += h, h => serverContext.Faulted -= h, @@ -220,6 +187,7 @@ public async Task Faulted_TestAsync() } catch { + // do nothing } }); @@ -240,4 +208,12 @@ public async Task ServiceStateChanged_TestAsync() Assert.True(result); await serverContext.ReleaseAsync(token); } + + private sealed class TestService1 : ServerService, ITestService1 + { + } + + private sealed class TestService2 : ServerService, ITestService2 + { + } } diff --git a/test/JSSoft.Communication.Tests/TestLogger.cs b/test/JSSoft.Communication.Tests/TestLogger.cs index 2fabe3a..ff2e341 100644 --- a/test/JSSoft.Communication.Tests/TestLogger.cs +++ b/test/JSSoft.Communication.Tests/TestLogger.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// using JSSoft.Communication.Logging; using Xunit.Abstractions; diff --git a/test/JSSoft.Communication.Tests/UnitTest1.cs b/test/JSSoft.Communication.Tests/UnitTest1.cs index 99a0c56..70fede0 100644 --- a/test/JSSoft.Communication.Tests/UnitTest1.cs +++ b/test/JSSoft.Communication.Tests/UnitTest1.cs @@ -1,24 +1,7 @@ -// MIT License -// -// Copyright (c) 2024 Jeesu Choi -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. +// +// Copyright (c) 2024 Jeesu Choi. All Rights Reserved. +// Licensed under the MIT License. See LICENSE.md in the project root for license information. +// namespace JSSoft.Communication.Tests; @@ -34,21 +17,6 @@ public interface ITestClient void OnMessageSend(string message); } - sealed class TestServer : ServerService, ITestServer - { - public async Task SendMessageAsync(string message, CancellationToken cancellationToken) - { - await Task.Delay(1000, cancellationToken); - } - } - - sealed class TestClient : ClientService, ITestClient - { - public void OnMessageSend(string message) - { - } - } - [Fact] public async Task OpenAndClientCloseAsync() { @@ -61,6 +29,9 @@ public async Task OpenAndClientCloseAsync() await clientContext.CloseAsync(clientToken, CancellationToken.None); await serverContext.CloseAsync(serverToken, CancellationToken.None); + + Assert.Equal(ServiceState.None, clientContext.ServiceState); + Assert.Equal(ServiceState.None, serverContext.ServiceState); } [Fact] @@ -70,9 +41,9 @@ public async Task OpenAndServerCloseAsync() var serverContext = new ServerContext(new TestServer()) { EndPoint = endPoint }; var clientContext = new ClientContext(new TestClient()) { EndPoint = endPoint }; - var serverToken = await serverContext.OpenAsync(CancellationToken.None); - var clientToken = await clientContext.OpenAsync(CancellationToken.None); var autoResetEvent = new AutoResetEvent(initialState: false); + var serverToken = await serverContext.OpenAsync(CancellationToken.None); + await clientContext.OpenAsync(CancellationToken.None); clientContext.Disconnected += (s, e) => autoResetEvent.Set(); await serverContext.CloseAsync(serverToken, CancellationToken.None); @@ -92,15 +63,19 @@ public async Task MultipleOpenAndClientCloseAsync() var count = 20; using var endPoint = new RandomEndPoint(); var serverContext = new ServerContext(new TestServer()) { EndPoint = endPoint }; - var clientContexts = Enumerable.Range(0, count).Select(item => new ClientContext(new TestClient()) { EndPoint = endPoint }).ToArray(); + var clientContexts = Enumerable.Range(0, count) + .Select(CreateClientContext) + .ToArray(); var serverToken = await serverContext.OpenAsync(CancellationToken.None); - var openTasks = clientContexts.Select(item => item.OpenAsync(CancellationToken.None)).ToArray(); + var openTasks = clientContexts.Select(item => item.OpenAsync(CancellationToken.None)) + .ToArray(); var tokens = await Task.WhenAll(openTasks); var closeTasks = new Task[count]; for (var i = 0; i < count; i++) { closeTasks[i] = clientContexts[i].CloseAsync(tokens[i], CancellationToken.None); } + await Task.WhenAll(closeTasks); await serverContext.CloseAsync(serverToken, CancellationToken.None); Assert.Equal(ServiceState.None, serverContext.ServiceState); @@ -108,6 +83,14 @@ public async Task MultipleOpenAndClientCloseAsync() { Assert.Equal(ServiceState.None, clientContexts[i].ServiceState); } + + ClientContext CreateClientContext(int index) + { + return new ClientContext(new TestClient()) + { + EndPoint = endPoint, + }; + } } [Fact] @@ -126,5 +109,23 @@ public async Task OpenAndInvokeAndClientCloseAsync() await clientContext.CloseAsync(clientToken, CancellationToken.None); await serverContext.CloseAsync(serverToken, CancellationToken.None); + + Assert.Equal(ServiceState.None, clientContext.ServiceState); + Assert.Equal(ServiceState.None, serverContext.ServiceState); + } + + private sealed class TestServer : ServerService, ITestServer + { + public async Task SendMessageAsync(string message, CancellationToken cancellationToken) + { + await Task.Delay(1000, cancellationToken); + } + } + + private sealed class TestClient : ClientService, ITestClient + { + public void OnMessageSend(string message) + { + } } }