diff --git a/AdaptiveRemote.sln b/AdaptiveRemote.sln index 09fb47c..d8efcc5 100644 --- a/AdaptiveRemote.sln +++ b/AdaptiveRemote.sln @@ -7,7 +7,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRemote", "src\Adapt EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdaptiveRemote.Console", "src\AdaptiveRemote.Console\AdaptiveRemote.Console.csproj", "{345B73FC-07F9-490F-B566-2677D10B1834}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRemote.Tests", "test\AdaptiveRemote.Tests\AdaptiveRemote.Tests.csproj", "{99181C45-EACC-45CE-87B3-20A4AA28E793}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRemote.Core.Tests", "test\AdaptiveRemote.Core.Tests\AdaptiveRemote.Core.Tests.csproj", "{99181C45-EACC-45CE-87B3-20A4AA28E793}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRemote.Tests", "test\AdaptiveRemote.Tests\AdaptiveRemote.Tests.csproj", "{C3D4E5F6-7890-ABCD-EF12-345678901234}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRemote.Core", "src\AdaptiveRemote.Core\AdaptiveRemote.Core.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveRemote.Electron", "src\AdaptiveRemote.Electron\AdaptiveRemote.Electron.csproj", "{B2C3D4E5-F678-90AB-CDEF-123456789012}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" ProjectSection(SolutionItems) = preProject @@ -31,10 +37,22 @@ Global {99181C45-EACC-45CE-87B3-20A4AA28E793}.Debug|Any CPU.Build.0 = Debug|Any CPU {99181C45-EACC-45CE-87B3-20A4AA28E793}.Release|Any CPU.ActiveCfg = Release|Any CPU {99181C45-EACC-45CE-87B3-20A4AA28E793}.Release|Any CPU.Build.0 = Release|Any CPU + {C3D4E5F6-7890-ABCD-EF12-345678901234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3D4E5F6-7890-ABCD-EF12-345678901234}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3D4E5F6-7890-ABCD-EF12-345678901234}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3D4E5F6-7890-ABCD-EF12-345678901234}.Release|Any CPU.Build.0 = Release|Any CPU {345B73FC-07F9-490F-B566-2677D10B1834}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {345B73FC-07F9-490F-B566-2677D10B1834}.Debug|Any CPU.Build.0 = Debug|Any CPU {345B73FC-07F9-490F-B566-2677D10B1834}.Release|Any CPU.ActiveCfg = Release|Any CPU {345B73FC-07F9-490F-B566-2677D10B1834}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU + {B2C3D4E5-F678-90AB-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2C3D4E5-F678-90AB-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2C3D4E5-F678-90AB-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2C3D4E5-F678-90AB-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02bfa19..ca125cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,6 +19,8 @@ templates for [bug reports](../.github/ISSUE_TEMPLATE/bug_report.md) and - **Testing:** - Add or update unit tests as appropriate for your changes. - All tests must pass before your pull request will be considered. + - **Use Moq for mocking:** When creating test doubles, prefer using Moq over creating fake classes. + This keeps test code consistent and reduces the number of custom test helper classes. - **Documentation:** - Architecture and design notes are stored alongside implementations using `_doc_*.md` filenames so they surface at the top of each folder. - Living documentation files should: diff --git a/src/AdaptiveRemote.Console/AdaptiveRemote.Console.csproj b/src/AdaptiveRemote.Console/AdaptiveRemote.Console.csproj index cb918c7..a0fc0f7 100644 --- a/src/AdaptiveRemote.Console/AdaptiveRemote.Console.csproj +++ b/src/AdaptiveRemote.Console/AdaptiveRemote.Console.csproj @@ -8,7 +8,7 @@ - + wwwroot\%(RecursiveDir)%(Filename)%(Extension) PreserveNewest true diff --git a/src/AdaptiveRemote.Core/AdaptiveRemote.Core.csproj b/src/AdaptiveRemote.Core/AdaptiveRemote.Core.csproj new file mode 100644 index 0000000..232cd00 --- /dev/null +++ b/src/AdaptiveRemote.Core/AdaptiveRemote.Core.csproj @@ -0,0 +1,51 @@ + + + + net8.0 + enable + enable + AdaptiveRemote + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + True + True + LoggingMessages.resx + + + + + + ResXFileCodeGenerator + LoggingMessages.Designer.cs + + + + diff --git a/src/AdaptiveRemote/Components/CommandButton.razor b/src/AdaptiveRemote.Core/Components/CommandButton.razor similarity index 100% rename from src/AdaptiveRemote/Components/CommandButton.razor rename to src/AdaptiveRemote.Core/Components/CommandButton.razor diff --git a/src/AdaptiveRemote/Components/ConversationUI.razor b/src/AdaptiveRemote.Core/Components/ConversationUI.razor similarity index 100% rename from src/AdaptiveRemote/Components/ConversationUI.razor rename to src/AdaptiveRemote.Core/Components/ConversationUI.razor diff --git a/src/AdaptiveRemote/Components/LoadingScreen.razor b/src/AdaptiveRemote.Core/Components/LoadingScreen.razor similarity index 100% rename from src/AdaptiveRemote/Components/LoadingScreen.razor rename to src/AdaptiveRemote.Core/Components/LoadingScreen.razor diff --git a/src/AdaptiveRemote/Components/Remote.razor b/src/AdaptiveRemote.Core/Components/Remote.razor similarity index 100% rename from src/AdaptiveRemote/Components/Remote.razor rename to src/AdaptiveRemote.Core/Components/Remote.razor diff --git a/src/AdaptiveRemote/Components/RemoteLayout.razor b/src/AdaptiveRemote.Core/Components/RemoteLayout.razor similarity index 100% rename from src/AdaptiveRemote/Components/RemoteLayout.razor rename to src/AdaptiveRemote.Core/Components/RemoteLayout.razor diff --git a/src/AdaptiveRemote/Components/Root.razor b/src/AdaptiveRemote.Core/Components/Root.razor similarity index 100% rename from src/AdaptiveRemote/Components/Root.razor rename to src/AdaptiveRemote.Core/Components/Root.razor diff --git a/src/AdaptiveRemote/Components/_doc_UI.md b/src/AdaptiveRemote.Core/Components/_doc_UI.md similarity index 100% rename from src/AdaptiveRemote/Components/_doc_UI.md rename to src/AdaptiveRemote.Core/Components/_doc_UI.md diff --git a/src/AdaptiveRemote/Configuration/BroadlinkHostBuilderExtensions.cs b/src/AdaptiveRemote.Core/Configuration/BroadlinkHostBuilderExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Configuration/BroadlinkHostBuilderExtensions.cs rename to src/AdaptiveRemote.Core/Configuration/BroadlinkHostBuilderExtensions.cs diff --git a/src/AdaptiveRemote/Configuration/ConversationHostBuilderExtensions.cs b/src/AdaptiveRemote.Core/Configuration/ConversationHostBuilderExtensions.cs similarity index 56% rename from src/AdaptiveRemote/Configuration/ConversationHostBuilderExtensions.cs rename to src/AdaptiveRemote.Core/Configuration/ConversationHostBuilderExtensions.cs index 9b02ed2..d7af7f8 100644 --- a/src/AdaptiveRemote/Configuration/ConversationHostBuilderExtensions.cs +++ b/src/AdaptiveRemote.Core/Configuration/ConversationHostBuilderExtensions.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; namespace AdaptiveRemote.Configuration; @@ -17,19 +16,13 @@ internal static IServiceCollection AddConversationServices(this IServiceCollecti .AddScopedLifecycleService() .AddScoped() .AddScoped() - .AddScoped() .AddScoped() - .AddSingleton() - .AddSingleton() - .AddSingleton() .AddSingleton() .AddScoped(GetConversationViewModel); internal static IServiceCollection AddConversationServices(this IServiceCollection services, IConfiguration config) => services .AddConversationServices() - .OptionallyAddFakeSpeechRecognition(config) - .OptionallyAddSamplesRecorder(config) .Configure(config); private static Models.ConversationView GetConversationViewModel(IServiceProvider provider) @@ -37,16 +30,4 @@ private static Models.ConversationView GetConversationViewModel(IServiceProvider IRemoteDefinitionService definition = provider.GetRequiredService(); return definition.GetElement(); } - - private static IServiceCollection OptionallyAddFakeSpeechRecognition(this IServiceCollection services, IConfiguration config) - => config.GetValue(nameof(ConversationSettings.Fake)) == true - ? services.AddSingleton() - : services; - - private static IServiceCollection OptionallyAddSamplesRecorder(this IServiceCollection services, IConfiguration config) - => config.GetValue(nameof(ConversationSettings.RecordSamples)) == false - ? services - : services - .AddHostedService() - .AddSingleton(); } diff --git a/src/AdaptiveRemote.Core/Configuration/CoreHostBuilderExtensions.cs b/src/AdaptiveRemote.Core/Configuration/CoreHostBuilderExtensions.cs new file mode 100644 index 0000000..f65dbe1 --- /dev/null +++ b/src/AdaptiveRemote.Core/Configuration/CoreHostBuilderExtensions.cs @@ -0,0 +1,35 @@ +using AdaptiveRemote.Models; +using AdaptiveRemote.Services; +using AdaptiveRemote.Services.Lifecycle; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace AdaptiveRemote.Configuration; + +/// +/// Public extension methods for configuring the cross-platform app +/// +public static class CoreHostBuilderExtensions +{ + /// + /// Configures the core application services shared by all hosts + /// + public static IHostBuilder ConfigureCoreApp(this IHostBuilder hostBuilder) + => hostBuilder + .ConfigureTelemetry() + .AddRemoteServices() + .AddBroadlinkSupport() + .AddTiVoSupport() + .AddConversationSystem() + .AddSystemWrapperServices(); + + /// + /// Adds the lifecycle view model and controller for hosting + /// + public static IServiceCollection AddLifecycleServices(this IServiceCollection services, LifecycleView lifecycleView, ILifecycleViewController lifecycleController) + { + services.AddSingleton(lifecycleView); + services.AddSingleton(lifecycleController); + return services; + } +} diff --git a/src/AdaptiveRemote/Configuration/HostBuilderExtensions.cs b/src/AdaptiveRemote.Core/Configuration/HostBuilderExtensions.cs similarity index 95% rename from src/AdaptiveRemote/Configuration/HostBuilderExtensions.cs rename to src/AdaptiveRemote.Core/Configuration/HostBuilderExtensions.cs index 5e64c82..a913b29 100644 --- a/src/AdaptiveRemote/Configuration/HostBuilderExtensions.cs +++ b/src/AdaptiveRemote.Core/Configuration/HostBuilderExtensions.cs @@ -14,7 +14,6 @@ internal static IHostBuilder AddRemoteServices(this IHostBuilder builder) internal static IServiceCollection AddRemoteServices(this IServiceCollection services) => services - .AddSingleton() .AddHostedService() .AddScopedLifecycleService() .AddScoped() diff --git a/src/AdaptiveRemote/Configuration/SettingsKeys.cs b/src/AdaptiveRemote.Core/Configuration/SettingsKeys.cs similarity index 100% rename from src/AdaptiveRemote/Configuration/SettingsKeys.cs rename to src/AdaptiveRemote.Core/Configuration/SettingsKeys.cs diff --git a/src/AdaptiveRemote/Configuration/SystemWrapperHostBuilderExtensions.cs b/src/AdaptiveRemote.Core/Configuration/SystemWrapperHostBuilderExtensions.cs similarity index 73% rename from src/AdaptiveRemote/Configuration/SystemWrapperHostBuilderExtensions.cs rename to src/AdaptiveRemote.Core/Configuration/SystemWrapperHostBuilderExtensions.cs index 35d761c..d30c083 100644 --- a/src/AdaptiveRemote/Configuration/SystemWrapperHostBuilderExtensions.cs +++ b/src/AdaptiveRemote.Core/Configuration/SystemWrapperHostBuilderExtensions.cs @@ -7,10 +7,10 @@ namespace AdaptiveRemote.Configuration; internal static class SystemWrapperHostBuilderExtensions { - public static IHostBuilder AddSystemWrapperServices(this IHostBuilder builder) + internal static IHostBuilder AddSystemWrapperServices(this IHostBuilder builder) => builder.ConfigureServices(services => services.AddSystemWrappers()); - public static IServiceCollection AddSystemWrappers(this IServiceCollection services) + internal static IServiceCollection AddSystemWrappers(this IServiceCollection services) => services .AddSingleton() .AddSingleton(); diff --git a/src/AdaptiveRemote/Configuration/TelemetryHostBuilderExtensions.cs b/src/AdaptiveRemote.Core/Configuration/TelemetryHostBuilderExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Configuration/TelemetryHostBuilderExtensions.cs rename to src/AdaptiveRemote.Core/Configuration/TelemetryHostBuilderExtensions.cs diff --git a/src/AdaptiveRemote/Configuration/TiVoHostBuilderExtensions.cs b/src/AdaptiveRemote.Core/Configuration/TiVoHostBuilderExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Configuration/TiVoHostBuilderExtensions.cs rename to src/AdaptiveRemote.Core/Configuration/TiVoHostBuilderExtensions.cs diff --git a/src/AdaptiveRemote/GlobalAttributes.cs b/src/AdaptiveRemote.Core/GlobalAttributes.cs similarity index 81% rename from src/AdaptiveRemote/GlobalAttributes.cs rename to src/AdaptiveRemote.Core/GlobalAttributes.cs index 69dea27..57d8af1 100644 --- a/src/AdaptiveRemote/GlobalAttributes.cs +++ b/src/AdaptiveRemote.Core/GlobalAttributes.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("AdaptiveRemote.Tests")] +[assembly: InternalsVisibleTo("AdaptiveRemote")] +[assembly: InternalsVisibleTo("AdaptiveRemote.Core.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/AdaptiveRemote/GlobalSuppressions.cs b/src/AdaptiveRemote.Core/GlobalSuppressions.cs similarity index 100% rename from src/AdaptiveRemote/GlobalSuppressions.cs rename to src/AdaptiveRemote.Core/GlobalSuppressions.cs diff --git a/src/AdaptiveRemote/GlobalUsings.cs b/src/AdaptiveRemote.Core/GlobalUsings.cs similarity index 100% rename from src/AdaptiveRemote/GlobalUsings.cs rename to src/AdaptiveRemote.Core/GlobalUsings.cs diff --git a/src/AdaptiveRemote/Logging/ILoggerExtensions.cs b/src/AdaptiveRemote.Core/Logging/ILoggerExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Logging/ILoggerExtensions.cs rename to src/AdaptiveRemote.Core/Logging/ILoggerExtensions.cs diff --git a/src/AdaptiveRemote/Logging/LoggingMessages.Designer.cs b/src/AdaptiveRemote.Core/Logging/LoggingMessages.Designer.cs similarity index 100% rename from src/AdaptiveRemote/Logging/LoggingMessages.Designer.cs rename to src/AdaptiveRemote.Core/Logging/LoggingMessages.Designer.cs diff --git a/src/AdaptiveRemote/Logging/LoggingMessages.resx b/src/AdaptiveRemote.Core/Logging/LoggingMessages.resx similarity index 100% rename from src/AdaptiveRemote/Logging/LoggingMessages.resx rename to src/AdaptiveRemote.Core/Logging/LoggingMessages.resx diff --git a/src/AdaptiveRemote/Logging/Message.cs b/src/AdaptiveRemote.Core/Logging/Message.cs similarity index 100% rename from src/AdaptiveRemote/Logging/Message.cs rename to src/AdaptiveRemote.Core/Logging/Message.cs diff --git a/src/AdaptiveRemote/Models/ActionCommand.cs b/src/AdaptiveRemote.Core/Models/ActionCommand.cs similarity index 100% rename from src/AdaptiveRemote/Models/ActionCommand.cs rename to src/AdaptiveRemote.Core/Models/ActionCommand.cs diff --git a/src/AdaptiveRemote/Models/Command.cs b/src/AdaptiveRemote.Core/Models/Command.cs similarity index 100% rename from src/AdaptiveRemote/Models/Command.cs rename to src/AdaptiveRemote.Core/Models/Command.cs diff --git a/src/AdaptiveRemote/Models/ConversationView.cs b/src/AdaptiveRemote.Core/Models/ConversationView.cs similarity index 100% rename from src/AdaptiveRemote/Models/ConversationView.cs rename to src/AdaptiveRemote.Core/Models/ConversationView.cs diff --git a/src/AdaptiveRemote/Models/IRCommand.cs b/src/AdaptiveRemote.Core/Models/IRCommand.cs similarity index 100% rename from src/AdaptiveRemote/Models/IRCommand.cs rename to src/AdaptiveRemote.Core/Models/IRCommand.cs diff --git a/src/AdaptiveRemote/Models/LayoutGroup.cs b/src/AdaptiveRemote.Core/Models/LayoutGroup.cs similarity index 100% rename from src/AdaptiveRemote/Models/LayoutGroup.cs rename to src/AdaptiveRemote.Core/Models/LayoutGroup.cs diff --git a/src/AdaptiveRemote/Models/LifecycleCommand.cs b/src/AdaptiveRemote.Core/Models/LifecycleCommand.cs similarity index 100% rename from src/AdaptiveRemote/Models/LifecycleCommand.cs rename to src/AdaptiveRemote.Core/Models/LifecycleCommand.cs diff --git a/src/AdaptiveRemote/Models/LifecyclePhase.cs b/src/AdaptiveRemote.Core/Models/LifecyclePhase.cs similarity index 100% rename from src/AdaptiveRemote/Models/LifecyclePhase.cs rename to src/AdaptiveRemote.Core/Models/LifecyclePhase.cs diff --git a/src/AdaptiveRemote/Models/LifecycleView.cs b/src/AdaptiveRemote.Core/Models/LifecycleView.cs similarity index 100% rename from src/AdaptiveRemote/Models/LifecycleView.cs rename to src/AdaptiveRemote.Core/Models/LifecycleView.cs diff --git a/src/AdaptiveRemote/Models/Phrases.cs b/src/AdaptiveRemote.Core/Models/Phrases.cs similarity index 100% rename from src/AdaptiveRemote/Models/Phrases.cs rename to src/AdaptiveRemote.Core/Models/Phrases.cs diff --git a/src/AdaptiveRemote/Models/RemoteLayoutElement.cs b/src/AdaptiveRemote.Core/Models/RemoteLayoutElement.cs similarity index 100% rename from src/AdaptiveRemote/Models/RemoteLayoutElement.cs rename to src/AdaptiveRemote.Core/Models/RemoteLayoutElement.cs diff --git a/src/AdaptiveRemote/Models/TiVoCommand.cs b/src/AdaptiveRemote.Core/Models/TiVoCommand.cs similarity index 100% rename from src/AdaptiveRemote/Models/TiVoCommand.cs rename to src/AdaptiveRemote.Core/Models/TiVoCommand.cs diff --git a/src/AdaptiveRemote/Mvvm/MvvmObject.cs b/src/AdaptiveRemote.Core/Mvvm/MvvmObject.cs similarity index 100% rename from src/AdaptiveRemote/Mvvm/MvvmObject.cs rename to src/AdaptiveRemote.Core/Mvvm/MvvmObject.cs diff --git a/src/AdaptiveRemote/Mvvm/MvvmProperty.cs b/src/AdaptiveRemote.Core/Mvvm/MvvmProperty.cs similarity index 100% rename from src/AdaptiveRemote/Mvvm/MvvmProperty.cs rename to src/AdaptiveRemote.Core/Mvvm/MvvmProperty.cs diff --git a/src/AdaptiveRemote/Mvvm/_doc_Mvvm.md b/src/AdaptiveRemote.Core/Mvvm/_doc_Mvvm.md similarity index 100% rename from src/AdaptiveRemote/Mvvm/_doc_Mvvm.md rename to src/AdaptiveRemote.Core/Mvvm/_doc_Mvvm.md diff --git a/src/AdaptiveRemote/Services/Broadlink/AesWrapper.cs b/src/AdaptiveRemote.Core/Services/Broadlink/AesWrapper.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/AesWrapper.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/AesWrapper.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/AuthenticateRequestPayload.cs b/src/AdaptiveRemote.Core/Services/Broadlink/AuthenticateRequestPayload.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/AuthenticateRequestPayload.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/AuthenticateRequestPayload.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/AuthenticateResponsePayload.cs b/src/AdaptiveRemote.Core/Services/Broadlink/AuthenticateResponsePayload.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/AuthenticateResponsePayload.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/AuthenticateResponsePayload.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/BroadlinkCommandService.cs b/src/AdaptiveRemote.Core/Services/Broadlink/BroadlinkCommandService.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/BroadlinkCommandService.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/BroadlinkCommandService.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/BroadlinkException.cs b/src/AdaptiveRemote.Core/Services/Broadlink/BroadlinkException.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/BroadlinkException.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/BroadlinkException.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/BroadlinkSettings.cs b/src/AdaptiveRemote.Core/Services/Broadlink/BroadlinkSettings.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/BroadlinkSettings.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/BroadlinkSettings.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/CommandPayload.cs b/src/AdaptiveRemote.Core/Services/Broadlink/CommandPayload.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/CommandPayload.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/CommandPayload.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/DeviceConnection.cs b/src/AdaptiveRemote.Core/Services/Broadlink/DeviceConnection.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/DeviceConnection.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/DeviceConnection.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/DeviceLocator.cs b/src/AdaptiveRemote.Core/Services/Broadlink/DeviceLocator.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/DeviceLocator.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/DeviceLocator.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/IDeviceConnection.cs b/src/AdaptiveRemote.Core/Services/Broadlink/IDeviceConnection.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/IDeviceConnection.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/IDeviceConnection.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/IDeviceLocator.cs b/src/AdaptiveRemote.Core/Services/Broadlink/IDeviceLocator.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/IDeviceLocator.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/IDeviceLocator.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/IEncryption.cs b/src/AdaptiveRemote.Core/Services/Broadlink/IEncryption.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/IEncryption.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/IEncryption.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/ISocket.cs b/src/AdaptiveRemote.Core/Services/Broadlink/ISocket.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/ISocket.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/ISocket.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/IUdpService.cs b/src/AdaptiveRemote.Core/Services/Broadlink/IUdpService.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/IUdpService.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/IUdpService.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/Payload.cs b/src/AdaptiveRemote.Core/Services/Broadlink/Payload.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/Payload.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/Payload.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/ResponsePacket.cs b/src/AdaptiveRemote.Core/Services/Broadlink/ResponsePacket.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/ResponsePacket.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/ResponsePacket.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/ResponsePacketHeader.cs b/src/AdaptiveRemote.Core/Services/Broadlink/ResponsePacketHeader.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/ResponsePacketHeader.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/ResponsePacketHeader.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/ScanRequestPacket.cs b/src/AdaptiveRemote.Core/Services/Broadlink/ScanRequestPacket.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/ScanRequestPacket.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/ScanRequestPacket.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/ScanResponsePacket.cs b/src/AdaptiveRemote.Core/Services/Broadlink/ScanResponsePacket.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/ScanResponsePacket.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/ScanResponsePacket.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/SendPacket.cs b/src/AdaptiveRemote.Core/Services/Broadlink/SendPacket.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/SendPacket.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/SendPacket.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/SendPacketHeader.cs b/src/AdaptiveRemote.Core/Services/Broadlink/SendPacketHeader.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/SendPacketHeader.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/SendPacketHeader.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/SocketWrapper.cs b/src/AdaptiveRemote.Core/Services/Broadlink/SocketWrapper.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/SocketWrapper.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/SocketWrapper.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/UdpException.cs b/src/AdaptiveRemote.Core/Services/Broadlink/UdpException.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/UdpException.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/UdpException.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/UdpService.cs b/src/AdaptiveRemote.Core/Services/Broadlink/UdpService.cs similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/UdpService.cs rename to src/AdaptiveRemote.Core/Services/Broadlink/UdpService.cs diff --git a/src/AdaptiveRemote/Services/Broadlink/_doc_Broadlink.md b/src/AdaptiveRemote.Core/Services/Broadlink/_doc_Broadlink.md similarity index 100% rename from src/AdaptiveRemote/Services/Broadlink/_doc_Broadlink.md rename to src/AdaptiveRemote.Core/Services/Broadlink/_doc_Broadlink.md diff --git a/src/AdaptiveRemote/Services/CommandServiceBase.cs b/src/AdaptiveRemote.Core/Services/CommandServiceBase.cs similarity index 100% rename from src/AdaptiveRemote/Services/CommandServiceBase.cs rename to src/AdaptiveRemote.Core/Services/CommandServiceBase.cs diff --git a/src/AdaptiveRemote/Services/Commands/NullCommandService.cs b/src/AdaptiveRemote.Core/Services/Commands/NullCommandService.cs similarity index 100% rename from src/AdaptiveRemote/Services/Commands/NullCommandService.cs rename to src/AdaptiveRemote.Core/Services/Commands/NullCommandService.cs diff --git a/src/AdaptiveRemote/Services/Commands/StaticCommandGroupProvider.cs b/src/AdaptiveRemote.Core/Services/Commands/StaticCommandGroupProvider.cs similarity index 100% rename from src/AdaptiveRemote/Services/Commands/StaticCommandGroupProvider.cs rename to src/AdaptiveRemote.Core/Services/Commands/StaticCommandGroupProvider.cs diff --git a/src/AdaptiveRemote/Services/Commands/_doc_Commands.md b/src/AdaptiveRemote.Core/Services/Commands/_doc_Commands.md similarity index 100% rename from src/AdaptiveRemote/Services/Commands/_doc_Commands.md rename to src/AdaptiveRemote.Core/Services/Commands/_doc_Commands.md diff --git a/src/AdaptiveRemote/Services/Conversation/ConversationController.cs b/src/AdaptiveRemote.Core/Services/Conversation/ConversationController.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ConversationController.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ConversationController.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ConversationResponse.cs b/src/AdaptiveRemote.Core/Services/Conversation/ConversationResponse.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ConversationResponse.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ConversationResponse.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ConversationSettings.cs b/src/AdaptiveRemote.Core/Services/Conversation/ConversationSettings.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ConversationSettings.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ConversationSettings.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ConversationState.cs b/src/AdaptiveRemote.Core/Services/Conversation/ConversationState.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ConversationState.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ConversationState.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ConversationStateExtensions.cs b/src/AdaptiveRemote.Core/Services/Conversation/ConversationStateExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ConversationStateExtensions.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ConversationStateExtensions.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ConversationStateMachine.cs b/src/AdaptiveRemote.Core/Services/Conversation/ConversationStateMachine.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ConversationStateMachine.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ConversationStateMachine.cs diff --git a/src/AdaptiveRemote.Core/Services/Conversation/IGrammar.cs b/src/AdaptiveRemote.Core/Services/Conversation/IGrammar.cs new file mode 100644 index 0000000..bb10155 --- /dev/null +++ b/src/AdaptiveRemote.Core/Services/Conversation/IGrammar.cs @@ -0,0 +1,17 @@ +namespace AdaptiveRemote.Services.Conversation; + +/// +/// Platform-independent grammar interface that abstracts System.Speech.Recognition.Grammar +/// +public interface IGrammar +{ + /// + /// Gets or sets the name of the grammar. + /// + string Name { get; } + + /// + /// Gets or sets whether the grammar is enabled for recognition. + /// + bool Enabled { get; set; } +} diff --git a/src/AdaptiveRemote.Core/Services/Conversation/IGrammarProvider.cs b/src/AdaptiveRemote.Core/Services/Conversation/IGrammarProvider.cs new file mode 100644 index 0000000..a8fe1e3 --- /dev/null +++ b/src/AdaptiveRemote.Core/Services/Conversation/IGrammarProvider.cs @@ -0,0 +1,12 @@ +namespace AdaptiveRemote.Services.Conversation; + +/// +/// Loader for Grammar objects +/// +public interface IGrammarProvider +{ + /// + /// Load a grammar that supports the given kind of phrases + /// + IGrammar LoadGrammar(PhraseKinds phraseKind); +} diff --git a/src/AdaptiveRemote/Services/Conversation/IListeningController.cs b/src/AdaptiveRemote.Core/Services/Conversation/IListeningController.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/IListeningController.cs rename to src/AdaptiveRemote.Core/Services/Conversation/IListeningController.cs diff --git a/src/AdaptiveRemote/Services/Conversation/IRecognizedSpeech.cs b/src/AdaptiveRemote.Core/Services/Conversation/IRecognizedSpeech.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/IRecognizedSpeech.cs rename to src/AdaptiveRemote.Core/Services/Conversation/IRecognizedSpeech.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ISpeechRecognition.cs b/src/AdaptiveRemote.Core/Services/Conversation/ISpeechRecognition.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ISpeechRecognition.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ISpeechRecognition.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ISpeechRecognitionEngine.cs b/src/AdaptiveRemote.Core/Services/Conversation/ISpeechRecognitionEngine.cs similarity index 82% rename from src/AdaptiveRemote/Services/Conversation/ISpeechRecognitionEngine.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ISpeechRecognitionEngine.cs index 0cee7aa..a2fcc96 100644 --- a/src/AdaptiveRemote/Services/Conversation/ISpeechRecognitionEngine.cs +++ b/src/AdaptiveRemote.Core/Services/Conversation/ISpeechRecognitionEngine.cs @@ -1,11 +1,9 @@ -using System.Speech.Recognition; - -namespace AdaptiveRemote.Services.Conversation; +namespace AdaptiveRemote.Services.Conversation; /// -/// Think wrapper around System.Speech.Recognition.SpeechRecognitionEngine. +/// Thin wrapper around System.Speech.Recognition.SpeechRecognitionEngine. /// -internal interface ISpeechRecognitionEngine +public interface ISpeechRecognitionEngine { /// /// Raised when the SpeechRecognitionEngine receives input that matches any of its @@ -22,12 +20,12 @@ internal interface ISpeechRecognitionEngine /// /// Synchronously loads a Grammar object. /// - void LoadGrammar(Grammar grammar); + void LoadGrammar(IGrammar grammar); /// /// Unloads a specified Grammar object from the SpeechRecognitionEngine instance. /// - void UnloadGrammar(Grammar grammar); + void UnloadGrammar(IGrammar grammar); /// /// Unloads all Grammar objects from the recognizer. diff --git a/src/AdaptiveRemote/Services/Conversation/ISpeechSynthesis.cs b/src/AdaptiveRemote.Core/Services/Conversation/ISpeechSynthesis.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ISpeechSynthesis.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ISpeechSynthesis.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ISpeechSynthesizer.cs b/src/AdaptiveRemote.Core/Services/Conversation/ISpeechSynthesizer.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ISpeechSynthesizer.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ISpeechSynthesizer.cs diff --git a/src/AdaptiveRemote/Services/Conversation/ListeningController.cs b/src/AdaptiveRemote.Core/Services/Conversation/ListeningController.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/ListeningController.cs rename to src/AdaptiveRemote.Core/Services/Conversation/ListeningController.cs diff --git a/src/AdaptiveRemote/Services/Conversation/PhraseKinds.cs b/src/AdaptiveRemote.Core/Services/Conversation/PhraseKinds.cs similarity index 92% rename from src/AdaptiveRemote/Services/Conversation/PhraseKinds.cs rename to src/AdaptiveRemote.Core/Services/Conversation/PhraseKinds.cs index 3982cd1..96b954f 100644 --- a/src/AdaptiveRemote/Services/Conversation/PhraseKinds.cs +++ b/src/AdaptiveRemote.Core/Services/Conversation/PhraseKinds.cs @@ -1,7 +1,7 @@ namespace AdaptiveRemote.Services.Conversation; [Flags] -internal enum PhraseKinds +public enum PhraseKinds { WakeWord = 1, Commands = 2, diff --git a/src/AdaptiveRemote/Services/Conversation/RecognitionErrorException.cs b/src/AdaptiveRemote.Core/Services/Conversation/RecognitionErrorException.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/RecognitionErrorException.cs rename to src/AdaptiveRemote.Core/Services/Conversation/RecognitionErrorException.cs diff --git a/src/AdaptiveRemote/Services/Conversation/RecognizedSpeechEventArgs.cs b/src/AdaptiveRemote.Core/Services/Conversation/RecognizedSpeechEventArgs.cs similarity index 74% rename from src/AdaptiveRemote/Services/Conversation/RecognizedSpeechEventArgs.cs rename to src/AdaptiveRemote.Core/Services/Conversation/RecognizedSpeechEventArgs.cs index e58995e..a284553 100644 --- a/src/AdaptiveRemote/Services/Conversation/RecognizedSpeechEventArgs.cs +++ b/src/AdaptiveRemote.Core/Services/Conversation/RecognizedSpeechEventArgs.cs @@ -2,7 +2,7 @@ public class RecognizedSpeechEventArgs : EventArgs { - internal RecognizedSpeechEventArgs(IRecognizedSpeech result) + public RecognizedSpeechEventArgs(IRecognizedSpeech result) { Result = result; } diff --git a/src/AdaptiveRemote/Services/Conversation/SpeechRecognition.cs b/src/AdaptiveRemote.Core/Services/Conversation/SpeechRecognition.cs similarity index 92% rename from src/AdaptiveRemote/Services/Conversation/SpeechRecognition.cs rename to src/AdaptiveRemote.Core/Services/Conversation/SpeechRecognition.cs index 9cf45ee..05175e1 100644 --- a/src/AdaptiveRemote/Services/Conversation/SpeechRecognition.cs +++ b/src/AdaptiveRemote.Core/Services/Conversation/SpeechRecognition.cs @@ -1,5 +1,4 @@ -using System.Speech.Recognition; -using System.Threading.Channels; +using System.Threading.Channels; using AdaptiveRemote.Logging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -21,7 +20,7 @@ internal class SpeechRecognition : ISpeechRecognition private readonly IListeningController _listeningController; private readonly ILogger _logger; - private readonly IReadOnlyDictionary _grammars; + private readonly IReadOnlyDictionary _grammars; public SpeechRecognition(IOptions settings, ISpeechRecognitionEngine engine, IListeningController listeningController, IGrammarProvider grammarProvider, ILogger logger) { @@ -34,7 +33,7 @@ public SpeechRecognition(IOptions settings, ISpeechRecogni _grammars = GrammarKinds.ToDictionary(x => x, x => LoadGrammarIntoEngine(grammarProvider.LoadGrammar(x))); - Grammar LoadGrammarIntoEngine(Grammar grammar) + IGrammar LoadGrammarIntoEngine(IGrammar grammar) { grammar.Enabled = false; _engine.LoadGrammar(grammar); @@ -44,7 +43,7 @@ Grammar LoadGrammarIntoEngine(Grammar grammar) void ISpeechRecognition.SetFilter(PhraseKinds filter) { - foreach (KeyValuePair grammar in _grammars) + foreach (KeyValuePair grammar in _grammars) { grammar.Value.Enabled = filter.HasFlag(grammar.Key); } diff --git a/src/AdaptiveRemote/Services/Conversation/SpeechSynthesis.cs b/src/AdaptiveRemote.Core/Services/Conversation/SpeechSynthesis.cs similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/SpeechSynthesis.cs rename to src/AdaptiveRemote.Core/Services/Conversation/SpeechSynthesis.cs diff --git a/src/AdaptiveRemote/Services/Conversation/_doc_Conversation.md b/src/AdaptiveRemote.Core/Services/Conversation/_doc_Conversation.md similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/_doc_Conversation.md rename to src/AdaptiveRemote.Core/Services/Conversation/_doc_Conversation.md diff --git a/src/AdaptiveRemote/Services/Conversation/static_grammar.xml b/src/AdaptiveRemote.Core/Services/Conversation/static_grammar.xml similarity index 100% rename from src/AdaptiveRemote/Services/Conversation/static_grammar.xml rename to src/AdaptiveRemote.Core/Services/Conversation/static_grammar.xml diff --git a/src/AdaptiveRemote/Services/IBroadlinkService.cs b/src/AdaptiveRemote.Core/Services/IBroadlinkService.cs similarity index 100% rename from src/AdaptiveRemote/Services/IBroadlinkService.cs rename to src/AdaptiveRemote.Core/Services/IBroadlinkService.cs diff --git a/src/AdaptiveRemote/Services/ICommandService.cs b/src/AdaptiveRemote.Core/Services/ICommandService.cs similarity index 100% rename from src/AdaptiveRemote/Services/ICommandService.cs rename to src/AdaptiveRemote.Core/Services/ICommandService.cs diff --git a/src/AdaptiveRemote/Services/IFileSystem.cs b/src/AdaptiveRemote.Core/Services/IFileSystem.cs similarity index 100% rename from src/AdaptiveRemote/Services/IFileSystem.cs rename to src/AdaptiveRemote.Core/Services/IFileSystem.cs diff --git a/src/AdaptiveRemote/Services/IFileSystemExtensions.cs b/src/AdaptiveRemote.Core/Services/IFileSystemExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Services/IFileSystemExtensions.cs rename to src/AdaptiveRemote.Core/Services/IFileSystemExtensions.cs diff --git a/src/AdaptiveRemote/Services/ILifecycleActivity.cs b/src/AdaptiveRemote.Core/Services/ILifecycleActivity.cs similarity index 87% rename from src/AdaptiveRemote/Services/ILifecycleActivity.cs rename to src/AdaptiveRemote.Core/Services/ILifecycleActivity.cs index b0a6ef8..3f8d034 100644 --- a/src/AdaptiveRemote/Services/ILifecycleActivity.cs +++ b/src/AdaptiveRemote.Core/Services/ILifecycleActivity.cs @@ -1,6 +1,6 @@ namespace AdaptiveRemote.Services; -internal interface ILifecycleActivity : IDisposable +public interface ILifecycleActivity : IDisposable { /// /// The initialization status message associated with this activity. diff --git a/src/AdaptiveRemote/Services/ILifecycleViewController.cs b/src/AdaptiveRemote.Core/Services/ILifecycleViewController.cs similarity index 94% rename from src/AdaptiveRemote/Services/ILifecycleViewController.cs rename to src/AdaptiveRemote.Core/Services/ILifecycleViewController.cs index 67eae96..e341cf3 100644 --- a/src/AdaptiveRemote/Services/ILifecycleViewController.cs +++ b/src/AdaptiveRemote.Core/Services/ILifecycleViewController.cs @@ -1,6 +1,6 @@ namespace AdaptiveRemote.Services; -internal interface ILifecycleViewController +public interface ILifecycleViewController { /// /// Report an unrecoverable error that should stop the normal operation of diff --git a/src/AdaptiveRemote/Services/INetworking.cs b/src/AdaptiveRemote.Core/Services/INetworking.cs similarity index 100% rename from src/AdaptiveRemote/Services/INetworking.cs rename to src/AdaptiveRemote.Core/Services/INetworking.cs diff --git a/src/AdaptiveRemote/Services/IPersistSettings.cs b/src/AdaptiveRemote.Core/Services/IPersistSettings.cs similarity index 100% rename from src/AdaptiveRemote/Services/IPersistSettings.cs rename to src/AdaptiveRemote.Core/Services/IPersistSettings.cs diff --git a/src/AdaptiveRemote/Services/IPersistSettingsExtensions.cs b/src/AdaptiveRemote.Core/Services/IPersistSettingsExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Services/IPersistSettingsExtensions.cs rename to src/AdaptiveRemote.Core/Services/IPersistSettingsExtensions.cs diff --git a/src/AdaptiveRemote/Services/IRemoteDefinitionService.cs b/src/AdaptiveRemote.Core/Services/IRemoteDefinitionService.cs similarity index 100% rename from src/AdaptiveRemote/Services/IRemoteDefinitionService.cs rename to src/AdaptiveRemote.Core/Services/IRemoteDefinitionService.cs diff --git a/src/AdaptiveRemote/Services/IRemoteDefinitionServiceExtensions.cs b/src/AdaptiveRemote.Core/Services/IRemoteDefinitionServiceExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Services/IRemoteDefinitionServiceExtensions.cs rename to src/AdaptiveRemote.Core/Services/IRemoteDefinitionServiceExtensions.cs diff --git a/src/AdaptiveRemote/Services/IScopedLifecycle.cs b/src/AdaptiveRemote.Core/Services/IScopedLifecycle.cs similarity index 100% rename from src/AdaptiveRemote/Services/IScopedLifecycle.cs rename to src/AdaptiveRemote.Core/Services/IScopedLifecycle.cs diff --git a/src/AdaptiveRemote.Core/Services/Lifecycle/AcceleratedServices.cs b/src/AdaptiveRemote.Core/Services/Lifecycle/AcceleratedServices.cs new file mode 100644 index 0000000..f460f20 --- /dev/null +++ b/src/AdaptiveRemote.Core/Services/Lifecycle/AcceleratedServices.cs @@ -0,0 +1,101 @@ +using AdaptiveRemote.Configuration; +using AdaptiveRemote.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace AdaptiveRemote.Services.Lifecycle; + +/// +/// Manages the accelerated services that are created before the host starts +/// to provide immediate feedback to the user during startup. +/// This class should be used by both Windows and Electron hosts. +/// +public class AcceleratedServices +{ + private IHostBuilder _hostBuilder; + + /// + /// The lifecycle view model used to display startup status + /// + public LifecycleView ViewModel { get; } + + /// + /// The lifecycle view controller used to update startup status + /// + public ILifecycleViewController Controller { get; } + + /// + /// The diagnostic adapter for telemetry integration + /// + internal DiagnosticAdapter DiagnosticAdapter { get; } + + /// + /// Creates a new instance of AcceleratedServices with the given command line arguments + /// + public AcceleratedServices(string[] args) + { + ViewModel = new(); + Controller = new LifecycleViewController(ViewModel); + DiagnosticAdapter = new(Controller); + + Controller.SetPhase(LifecyclePhase.Waiting); + + _hostBuilder = Host.CreateDefaultBuilder(args) + .ConfigureCoreApp() + .ConfigureServices(services => services.AddLifecycleServices(ViewModel, Controller)); + } + + /// + /// Configure app settings for the host. Call this to set up configuration sources + /// specific to the host (e.g., user secrets, command line). + /// + public AcceleratedServices ConfigureAppSettings(Action configure) + { + _hostBuilder = _hostBuilder.ConfigureAppConfiguration(configure); + return this; + } + + /// + /// Add host-specific services. Windows host uses this to add MainWindow, BlazorWindowScopeFactory, + /// and Windows speech services. Electron host uses this to add ElectronScopeFactory and fake speech services. + /// + public AcceleratedServices ConfigureHostServices(Action configure) + { + _hostBuilder = _hostBuilder.ConfigureServices(configure); + return this; + } + + /// + /// Add host-specific services that require access to configuration. + /// + public AcceleratedServices ConfigureHostServices(Action configure) + { + _hostBuilder = _hostBuilder.ConfigureServices(configure); + return this; + } + + /// + /// Builds and runs the application host + /// + public async Task RunApplicationLoopAsync() + { + await Task.Run(async () => + { + try + { + IHost host = _hostBuilder.Build(); + await host.RunAsync(); + } + catch (Exception configErrors) + { + Controller.SetFatalError(configErrors); + throw; + } + finally + { + Controller.SetPhase(LifecyclePhase.CleaningUp); + } + }); + } +} diff --git a/src/AdaptiveRemote/Services/Lifecycle/ApplicationLifecycle.cs b/src/AdaptiveRemote.Core/Services/Lifecycle/ApplicationLifecycle.cs similarity index 100% rename from src/AdaptiveRemote/Services/Lifecycle/ApplicationLifecycle.cs rename to src/AdaptiveRemote.Core/Services/Lifecycle/ApplicationLifecycle.cs diff --git a/src/AdaptiveRemote/Services/Lifecycle/DiagnosticAdapter.cs b/src/AdaptiveRemote.Core/Services/Lifecycle/DiagnosticAdapter.cs similarity index 100% rename from src/AdaptiveRemote/Services/Lifecycle/DiagnosticAdapter.cs rename to src/AdaptiveRemote.Core/Services/Lifecycle/DiagnosticAdapter.cs diff --git a/src/AdaptiveRemote/Services/Lifecycle/IApplicationScope.cs b/src/AdaptiveRemote.Core/Services/Lifecycle/IApplicationScope.cs similarity index 100% rename from src/AdaptiveRemote/Services/Lifecycle/IApplicationScope.cs rename to src/AdaptiveRemote.Core/Services/Lifecycle/IApplicationScope.cs diff --git a/src/AdaptiveRemote/Services/Lifecycle/IApplicationScopeFactory.cs b/src/AdaptiveRemote.Core/Services/Lifecycle/IApplicationScopeFactory.cs similarity index 75% rename from src/AdaptiveRemote/Services/Lifecycle/IApplicationScopeFactory.cs rename to src/AdaptiveRemote.Core/Services/Lifecycle/IApplicationScopeFactory.cs index e3443e8..61cb9cc 100644 --- a/src/AdaptiveRemote/Services/Lifecycle/IApplicationScopeFactory.cs +++ b/src/AdaptiveRemote.Core/Services/Lifecycle/IApplicationScopeFactory.cs @@ -1,6 +1,6 @@ namespace AdaptiveRemote.Services.Lifecycle; -internal interface IApplicationScopeFactory +public interface IApplicationScopeFactory { Task CreateNewScopeAsync(CancellationToken cancellationToken); } diff --git a/src/AdaptiveRemote/Services/Lifecycle/LifecycleCommandService.cs b/src/AdaptiveRemote.Core/Services/Lifecycle/LifecycleCommandService.cs similarity index 100% rename from src/AdaptiveRemote/Services/Lifecycle/LifecycleCommandService.cs rename to src/AdaptiveRemote.Core/Services/Lifecycle/LifecycleCommandService.cs diff --git a/src/AdaptiveRemote/Services/Lifecycle/LifecycleViewController.cs b/src/AdaptiveRemote.Core/Services/Lifecycle/LifecycleViewController.cs similarity index 100% rename from src/AdaptiveRemote/Services/Lifecycle/LifecycleViewController.cs rename to src/AdaptiveRemote.Core/Services/Lifecycle/LifecycleViewController.cs diff --git a/src/AdaptiveRemote/Services/Lifecycle/_doc_Lifecycle.md b/src/AdaptiveRemote.Core/Services/Lifecycle/_doc_Lifecycle.md similarity index 100% rename from src/AdaptiveRemote/Services/Lifecycle/_doc_Lifecycle.md rename to src/AdaptiveRemote.Core/Services/Lifecycle/_doc_Lifecycle.md diff --git a/src/AdaptiveRemote/Services/ProgrammaticSettings/PersistSettings.cs b/src/AdaptiveRemote.Core/Services/ProgrammaticSettings/PersistSettings.cs similarity index 100% rename from src/AdaptiveRemote/Services/ProgrammaticSettings/PersistSettings.cs rename to src/AdaptiveRemote.Core/Services/ProgrammaticSettings/PersistSettings.cs diff --git a/src/AdaptiveRemote/Services/ProgrammaticSettings/ProgrammaticSettings.cs b/src/AdaptiveRemote.Core/Services/ProgrammaticSettings/ProgrammaticSettings.cs similarity index 100% rename from src/AdaptiveRemote/Services/ProgrammaticSettings/ProgrammaticSettings.cs rename to src/AdaptiveRemote.Core/Services/ProgrammaticSettings/ProgrammaticSettings.cs diff --git a/src/AdaptiveRemote/Services/ScopedBackgroundProcess.cs b/src/AdaptiveRemote.Core/Services/ScopedBackgroundProcess.cs similarity index 100% rename from src/AdaptiveRemote/Services/ScopedBackgroundProcess.cs rename to src/AdaptiveRemote.Core/Services/ScopedBackgroundProcess.cs diff --git a/src/AdaptiveRemote/Services/SystemWrappers/SystemIOWrapper.cs b/src/AdaptiveRemote.Core/Services/SystemWrappers/SystemIOWrapper.cs similarity index 100% rename from src/AdaptiveRemote/Services/SystemWrappers/SystemIOWrapper.cs rename to src/AdaptiveRemote.Core/Services/SystemWrappers/SystemIOWrapper.cs diff --git a/src/AdaptiveRemote/Services/SystemWrappers/SystemNetWrapper.cs b/src/AdaptiveRemote.Core/Services/SystemWrappers/SystemNetWrapper.cs similarity index 100% rename from src/AdaptiveRemote/Services/SystemWrappers/SystemNetWrapper.cs rename to src/AdaptiveRemote.Core/Services/SystemWrappers/SystemNetWrapper.cs diff --git a/src/AdaptiveRemote/Services/TiVo/ITiVoConnection.cs b/src/AdaptiveRemote.Core/Services/TiVo/ITiVoConnection.cs similarity index 100% rename from src/AdaptiveRemote/Services/TiVo/ITiVoConnection.cs rename to src/AdaptiveRemote.Core/Services/TiVo/ITiVoConnection.cs diff --git a/src/AdaptiveRemote/Services/TiVo/ITiVoLocator.cs b/src/AdaptiveRemote.Core/Services/TiVo/ITiVoLocator.cs similarity index 100% rename from src/AdaptiveRemote/Services/TiVo/ITiVoLocator.cs rename to src/AdaptiveRemote.Core/Services/TiVo/ITiVoLocator.cs diff --git a/src/AdaptiveRemote/Services/TiVo/LibraryTiVoConnection.cs b/src/AdaptiveRemote.Core/Services/TiVo/LibraryTiVoConnection.cs similarity index 100% rename from src/AdaptiveRemote/Services/TiVo/LibraryTiVoConnection.cs rename to src/AdaptiveRemote.Core/Services/TiVo/LibraryTiVoConnection.cs diff --git a/src/AdaptiveRemote/Services/TiVo/PreviousTiVoLocator.cs b/src/AdaptiveRemote.Core/Services/TiVo/PreviousTiVoLocator.cs similarity index 100% rename from src/AdaptiveRemote/Services/TiVo/PreviousTiVoLocator.cs rename to src/AdaptiveRemote.Core/Services/TiVo/PreviousTiVoLocator.cs diff --git a/src/AdaptiveRemote/Services/TiVo/ScanningTiVoLocator.cs b/src/AdaptiveRemote.Core/Services/TiVo/ScanningTiVoLocator.cs similarity index 100% rename from src/AdaptiveRemote/Services/TiVo/ScanningTiVoLocator.cs rename to src/AdaptiveRemote.Core/Services/TiVo/ScanningTiVoLocator.cs diff --git a/src/AdaptiveRemote/Services/TiVo/TiVoService.cs b/src/AdaptiveRemote.Core/Services/TiVo/TiVoService.cs similarity index 100% rename from src/AdaptiveRemote/Services/TiVo/TiVoService.cs rename to src/AdaptiveRemote.Core/Services/TiVo/TiVoService.cs diff --git a/src/AdaptiveRemote/Services/TiVo/TiVoSettings.cs b/src/AdaptiveRemote.Core/Services/TiVo/TiVoSettings.cs similarity index 100% rename from src/AdaptiveRemote/Services/TiVo/TiVoSettings.cs rename to src/AdaptiveRemote.Core/Services/TiVo/TiVoSettings.cs diff --git a/src/AdaptiveRemote/Services/_doc_Services.md b/src/AdaptiveRemote.Core/Services/_doc_Services.md similarity index 100% rename from src/AdaptiveRemote/Services/_doc_Services.md rename to src/AdaptiveRemote.Core/Services/_doc_Services.md diff --git a/src/AdaptiveRemote/Utilities/CancellationTokenExtensions.cs b/src/AdaptiveRemote.Core/Utilities/CancellationTokenExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Utilities/CancellationTokenExtensions.cs rename to src/AdaptiveRemote.Core/Utilities/CancellationTokenExtensions.cs diff --git a/src/AdaptiveRemote/Utilities/DecoratorServiceCollectionExtensions.cs b/src/AdaptiveRemote.Core/Utilities/DecoratorServiceCollectionExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Utilities/DecoratorServiceCollectionExtensions.cs rename to src/AdaptiveRemote.Core/Utilities/DecoratorServiceCollectionExtensions.cs diff --git a/src/AdaptiveRemote/Utilities/Errors.cs b/src/AdaptiveRemote.Core/Utilities/Errors.cs similarity index 100% rename from src/AdaptiveRemote/Utilities/Errors.cs rename to src/AdaptiveRemote.Core/Utilities/Errors.cs diff --git a/src/AdaptiveRemote/Utilities/StringExtensions.cs b/src/AdaptiveRemote.Core/Utilities/StringExtensions.cs similarity index 100% rename from src/AdaptiveRemote/Utilities/StringExtensions.cs rename to src/AdaptiveRemote.Core/Utilities/StringExtensions.cs diff --git a/src/AdaptiveRemote.Electron/AdaptiveRemote.Electron.csproj b/src/AdaptiveRemote.Electron/AdaptiveRemote.Electron.csproj new file mode 100644 index 0000000..ae59685 --- /dev/null +++ b/src/AdaptiveRemote.Electron/AdaptiveRemote.Electron.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + AdaptiveRemote + + + + + + + + + + + diff --git a/src/AdaptiveRemote.Electron/Pages/_Host.cshtml b/src/AdaptiveRemote.Electron/Pages/_Host.cshtml new file mode 100644 index 0000000..1ae2b9b --- /dev/null +++ b/src/AdaptiveRemote.Electron/Pages/_Host.cshtml @@ -0,0 +1,28 @@ +@page "/" +@using AdaptiveRemote.Components +@namespace AdaptiveRemote.Electron.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + Adaptive Remote + + + + + +
+ +
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + diff --git a/src/AdaptiveRemote.Electron/Program.cs b/src/AdaptiveRemote.Electron/Program.cs new file mode 100644 index 0000000..82a95df --- /dev/null +++ b/src/AdaptiveRemote.Electron/Program.cs @@ -0,0 +1,85 @@ +using AdaptiveRemote.Configuration; +using AdaptiveRemote.Models; +using AdaptiveRemote.Services; +using AdaptiveRemote.Services.Conversation; +using AdaptiveRemote.Services.Lifecycle; +using ElectronNET.API; +using ElectronNET.API.Entities; +using Microsoft.Extensions.DependencyInjection; + +// Create accelerated services using the Core pattern +var acceleratedServices = new AcceleratedServices(args); + +// Configure Electron-specific services +acceleratedServices.ConfigureHostServices(services => +{ + // Add Blazor Server services for Electron + services.AddRazorPages(); + services.AddServerSideBlazor(); + + // Add Electron-specific scope factory + services.AddSingleton(); + + // Add fake speech services for Electron (cross-platform without System.Speech) + services.AddSingleton(); + services.AddSingleton(); + services.AddScoped(); +}); + +// For Electron, we need to use a different startup pattern +// because it requires the ASP.NET Core web application builder +var builder = WebApplication.CreateBuilder(args); +builder.WebHost.UseElectron(args); + +// Add Blazor services +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +// Configure using Core's configuration +builder.Host.ConfigureCoreApp(); + +// Add lifecycle services +var lifecycleView = acceleratedServices.ViewModel; +var lifecycleController = acceleratedServices.Controller; +builder.Services.AddSingleton(lifecycleView); +builder.Services.AddSingleton(lifecycleController); + +// Add Electron-specific services +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + app.UseHsts(); +} + +app.UseStaticFiles(); +app.UseRouting(); + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +// Start Electron window - this makes it a desktop app, not just a web server +if (HybridSupport.IsElectronActive) +{ + _ = Task.Run(async () => + { + var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions + { + Width = 1200, + Height = 800, + Show = true, + Title = "Adaptive Remote" + }); + + window.OnClosed += () => Electron.App.Quit(); + }); +} + +app.Run(); diff --git a/src/AdaptiveRemote.Electron/Services/Conversation/FakeGrammar.cs b/src/AdaptiveRemote.Electron/Services/Conversation/FakeGrammar.cs new file mode 100644 index 0000000..a7cd63c --- /dev/null +++ b/src/AdaptiveRemote.Electron/Services/Conversation/FakeGrammar.cs @@ -0,0 +1,15 @@ +namespace AdaptiveRemote.Services.Conversation; + +/// +/// A simple fake grammar implementation for testing purposes +/// +internal class FakeGrammar : IGrammar +{ + public FakeGrammar(string name) + { + Name = name; + } + + public string Name { get; } + public bool Enabled { get; set; } +} diff --git a/src/AdaptiveRemote.Electron/Services/Conversation/FakeGrammarProvider.cs b/src/AdaptiveRemote.Electron/Services/Conversation/FakeGrammarProvider.cs new file mode 100644 index 0000000..f1a4cfc --- /dev/null +++ b/src/AdaptiveRemote.Electron/Services/Conversation/FakeGrammarProvider.cs @@ -0,0 +1,10 @@ +namespace AdaptiveRemote.Services.Conversation; + +/// +/// A fake grammar provider for testing purposes that creates FakeGrammar instances +/// +internal class FakeGrammarProvider : IGrammarProvider +{ + IGrammar IGrammarProvider.LoadGrammar(PhraseKinds phraseKind) + => new FakeGrammar(phraseKind.ToString()); +} diff --git a/src/AdaptiveRemote.Electron/Services/Conversation/FakeSpeechRecognitionEngine.cs b/src/AdaptiveRemote.Electron/Services/Conversation/FakeSpeechRecognitionEngine.cs new file mode 100644 index 0000000..6c5b6ec --- /dev/null +++ b/src/AdaptiveRemote.Electron/Services/Conversation/FakeSpeechRecognitionEngine.cs @@ -0,0 +1,128 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO; + +namespace AdaptiveRemote.Services.Conversation; + +internal class FakeSpeechRecognitionEngine : ISpeechRecognitionEngine +{ + private readonly Dictionary _grammars = new(); + + private event EventHandler? _recognized; + private TaskCompletionSource _pause = new(); + + public FakeSpeechRecognitionEngine() + { + _ = RecognitionLoop(); + } + + event EventHandler ISpeechRecognitionEngine.SpeechRecognized + { + add => _recognized += value; + remove => _recognized -= value; + } + + event EventHandler ISpeechRecognitionEngine.SpeechRejected + { + add { } + remove { } + } + + void ISpeechRecognitionEngine.LoadGrammar(IGrammar grammar) => _grammars.Add(grammar.Name, grammar); + void ISpeechRecognitionEngine.UnloadGrammar(IGrammar grammar) => _grammars.Remove(grammar.Name); + void ISpeechRecognitionEngine.UnloadAllGrammars() => _grammars.Clear(); + void ISpeechRecognitionEngine.RecognizeAsync() => _pause.TrySetResult(); + void ISpeechRecognitionEngine.RecognizeAsyncCancel() => _pause = new(); + void ISpeechRecognitionEngine.SetConfidenceThreshold(int threshold) { } + + private async Task RecognitionLoop() + { + IEnumerator commands = CommandLoop(); + int ticks = 0; + + while (true) + { + await _pause.Task; + await Task.Delay(1000); + ticks++; + + if (IsEnabled(PhraseKinds.WakeWord)) + { + if (ticks >= 5) + { + ticks = 0; + SendResult(new("Hey Remote", 80, ("system", "STARTLISTENING"))); + } + } + else if (IsEnabled(PhraseKinds.Commands)) + { + if (ticks >= 3) + { + ticks = 0; + commands.MoveNext(); + SendResult(commands.Current); + } + } + else + { + ticks = 0; + } + } + } + + private static IEnumerator CommandLoop() + { + while (true) + { + yield return Command("Go up", "Up"); + yield return Command("Down three", "Down", repeat: 3); + yield return new FakeRecognitionResult("That's wrong", 80, ("correction", "true")); + yield return Command("TiVo", "TiVo"); + yield return Command("Louder 5 times", "VolumeUp", repeat: 5); + yield return Command("Mute", "Mute", confidence: 40); + yield return new FakeRecognitionResult("Yes, I did", 80, ("confirmation", "YES")); + yield return Command("Volume Down 5", "VolumeDown", repeat: 5); + yield return Command("Guide", "Guide", confidence: 40); + yield return new FakeRecognitionResult("No", 80, ("confirmation", "NO")); + yield return Command("Go up", "Up"); + yield return new FakeRecognitionResult("That's wrong", 80, ("correction", "true")); + yield return Command("Back", "Back"); + yield return new FakeRecognitionResult("Thank you", 80, ("system", "STOPLISTENING"), ("thankyou", "true")); + } + + static FakeRecognitionResult Command(string text, string command, int confidence = 80, int? repeat = default) + => repeat is null + ? new FakeRecognitionResult(text, confidence, (nameof(command), command)) + : new FakeRecognitionResult(text, confidence, (nameof(command), command), (nameof(repeat), repeat.ToString()!)); + } + + private bool IsEnabled(PhraseKinds phraseKind) + => _grammars.TryGetValue(phraseKind.ToString(), out IGrammar? grammar) && grammar.Enabled; + + private void SendResult(FakeRecognitionResult result) + { + _recognized?.Invoke(this, new(result)); + } + + private class FakeRecognitionResult : IRecognizedSpeech + { + internal FakeRecognitionResult(string text, int confidence, params (string, string)[] semantics) + { + Text = text; + Confidence = confidence; + + _semantics = semantics; + } + + public string Text { get; } + public int Confidence { get; } + + private readonly (string, string)[] _semantics; + + bool IRecognizedSpeech.ContainsSemanticValue(string key) + => _semantics.Any(x => x.Item1 == key); + bool IRecognizedSpeech.TryGetSemanticValue(string key, [NotNullWhen(true)] out string? value) + => (value = _semantics.Where(x => x.Item1 == key).Select(x => x.Item2).FirstOrDefault()) is not null; + + void IRecognizedSpeech.WriteToWaveStream(Stream waveStream) => throw new NotImplementedException(); + } +} diff --git a/src/AdaptiveRemote.Electron/Services/Conversation/FakeSpeechSynthesizer.cs b/src/AdaptiveRemote.Electron/Services/Conversation/FakeSpeechSynthesizer.cs new file mode 100644 index 0000000..cc5de95 --- /dev/null +++ b/src/AdaptiveRemote.Electron/Services/Conversation/FakeSpeechSynthesizer.cs @@ -0,0 +1,33 @@ +namespace AdaptiveRemote.Services.Conversation; + +/// +/// A fake speech synthesizer for testing and cross-platform scenarios +/// +internal class FakeSpeechSynthesizer : ISpeechSynthesizer +{ + private event EventHandler? _speakCompleted; + + public void SpeakAsync(string phrase) + { + // Simulate async speech completion + Task.Run(async () => + { + await Task.Delay(100); + _speakCompleted?.Invoke(this, EventArgs.Empty); + }); + } + + public void CancelAll() { } + + public IEnumerable GetInstalledVoices() => ["Fake Voice"]; + + public void SelectVoice(string fullName) { } + + public void SetSpeakingRate(int rate) { } + + public event EventHandler SpeakCompleted + { + add => _speakCompleted += value; + remove => _speakCompleted -= value; + } +} diff --git a/src/AdaptiveRemote.Electron/Services/Lifecycle/ElectronScopeFactory.cs b/src/AdaptiveRemote.Electron/Services/Lifecycle/ElectronScopeFactory.cs new file mode 100644 index 0000000..868a1a8 --- /dev/null +++ b/src/AdaptiveRemote.Electron/Services/Lifecycle/ElectronScopeFactory.cs @@ -0,0 +1,38 @@ +using AdaptiveRemote.Services.Lifecycle; +using Microsoft.Extensions.DependencyInjection; + +namespace AdaptiveRemote.Services.Lifecycle; + +/// +/// IApplicationScopeFactory implementation for Electron/Blazor Server hosting +/// +internal class ElectronScopeFactory : IApplicationScopeFactory, IApplicationScope +{ + private readonly IServiceProvider _serviceProvider; + private IServiceScope? _scope; + + public ElectronScopeFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public Task CreateNewScopeAsync(CancellationToken cancellationToken) + { + _scope?.Dispose(); + _scope = _serviceProvider.CreateScope(); + return Task.FromResult(this); + } + + public async Task TryInvokeAsync(Func workItem, CancellationToken cancellationToken) + { + var scopeServices = _scope?.ServiceProvider ?? _serviceProvider; + await workItem(scopeServices, cancellationToken); + } + + public void Dispose() + { + _scope?.Dispose(); + _scope = null; + GC.SuppressFinalize(this); + } +} diff --git a/src/AdaptiveRemote.Electron/_Imports.razor b/src/AdaptiveRemote.Electron/_Imports.razor new file mode 100644 index 0000000..3d38647 --- /dev/null +++ b/src/AdaptiveRemote.Electron/_Imports.razor @@ -0,0 +1,3 @@ +@using Microsoft.AspNetCore.Components.Web +@using AdaptiveRemote.Components +@using AdaptiveRemote.Models diff --git a/src/AdaptiveRemote.Electron/appsettings.json b/src/AdaptiveRemote.Electron/appsettings.json new file mode 100644 index 0000000..70b751f --- /dev/null +++ b/src/AdaptiveRemote.Electron/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Conversation": { + "Fake": true + }, + "Broadlink": { + "Fake": true + }, + "TiVo": { + "Fake": true + } +} diff --git a/src/AdaptiveRemote.Electron/electron.manifest.json b/src/AdaptiveRemote.Electron/electron.manifest.json new file mode 100644 index 0000000..173e58c --- /dev/null +++ b/src/AdaptiveRemote.Electron/electron.manifest.json @@ -0,0 +1,34 @@ +{ + "executable": "AdaptiveRemote.Electron", + "splashscreen": { + "imageFile": "" + }, + "name": "Adaptive Remote", + "author": "", + "singleInstance": true, + "environment": "Production", + "build": { + "appId": "com.adaptiveremote.electron", + "productName": "Adaptive Remote", + "copyright": "", + "buildVersion": "1.0.0", + "compression": "maximum", + "directories": { + "output": "../../../artifacts" + }, + "extraResources": [ + { + "from": "./bin", + "to": "bin", + "filter": ["**/*"] + } + ], + "files": [ + { + "from": "./bin", + "to": "bin", + "filter": ["**/*"] + } + ] + } +} diff --git a/src/AdaptiveRemote/AdaptiveRemote.csproj b/src/AdaptiveRemote/AdaptiveRemote.csproj index b771323..c9ca9b8 100644 --- a/src/AdaptiveRemote/AdaptiveRemote.csproj +++ b/src/AdaptiveRemote/AdaptiveRemote.csproj @@ -17,47 +17,22 @@ - - - - <_ContentIncludedByDefault Remove="compilerconfig.json" /> - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - True - True - LoggingMessages.resx - + @@ -72,11 +47,4 @@ - - - ResXFileCodeGenerator - LoggingMessages.Designer.cs - - - diff --git a/src/AdaptiveRemote/App.xaml.cs b/src/AdaptiveRemote/App.xaml.cs index 143b188..f52ff58 100644 --- a/src/AdaptiveRemote/App.xaml.cs +++ b/src/AdaptiveRemote/App.xaml.cs @@ -11,7 +11,7 @@ protected override void OnStartup(StartupEventArgs e) { try { - AcceleratedServices accelerator = CreateAcceleratedServices(e.Args); + WindowsAcceleratedServices accelerator = CreateAcceleratedServices(e.Args); accelerator.MainWindow.Show(); accelerator.ViewModel.ShutdownCommand = new ActionCommand(Shutdown); @@ -31,9 +31,9 @@ protected override void OnStartup(StartupEventArgs e) } } - protected virtual AcceleratedServices CreateAcceleratedServices(string[] args) => new(args); + protected virtual WindowsAcceleratedServices CreateAcceleratedServices(string[] args) => new(args); - private async Task RunApplicationLoopAndShutdownAsync(AcceleratedServices accelerator) + private async Task RunApplicationLoopAndShutdownAsync(WindowsAcceleratedServices accelerator) { await accelerator.RunApplicationLoopAsync(); diff --git a/src/AdaptiveRemote/AppHostBuilderExtensions.cs b/src/AdaptiveRemote/AppHostBuilderExtensions.cs deleted file mode 100644 index b2284c3..0000000 --- a/src/AdaptiveRemote/AppHostBuilderExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AdaptiveRemote.Configuration; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace AdaptiveRemote; - -public static class AppHostBuilderExtensions -{ - public static IHostBuilder ConfigureApp(this IHostBuilder hostBuilder) - => hostBuilder - .AddBlazorUI() - .ConfigureTelemetry() - .AddRemoteServices() - .AddBroadlinkSupport() - .AddTiVoSupport() - .AddConversationSystem() - .AddSystemWrapperServices(); - - public static IHostBuilder ConfigureAppSettings(this IHostBuilder hostBuilder, string[] args) - => hostBuilder - .ConfigureAppConfiguration(config => - { - config.AddInMemoryCollection(new Dictionary - { - // This makes the default behavior to publish telemetry when the full application - // is hosted. However, the setting is "false" by default so that test hosting won't - // publish telemetry unless explicitly enabled. - // TODO [ADR-12]: This behavior is currently disabled because we don't have anywhere - // to publish telemetry to. - // ["telemetry:Publish"] = "True" - }); - config.AddUserSecrets(); - config.AddCommandLine(args); - }); - - private static IHostBuilder AddBlazorUI(this IHostBuilder hostBuilder) - { - return hostBuilder.ConfigureServices(services => services - .AddWpfBlazorWebView()); - } -} diff --git a/src/AdaptiveRemote/Services/Conversation/FakeSpeechRecognitionEngine.cs b/src/AdaptiveRemote/Services/Conversation/FakeSpeechRecognitionEngine.cs index c222295..6c5b6ec 100644 --- a/src/AdaptiveRemote/Services/Conversation/FakeSpeechRecognitionEngine.cs +++ b/src/AdaptiveRemote/Services/Conversation/FakeSpeechRecognitionEngine.cs @@ -1,12 +1,11 @@ using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Speech.Recognition; namespace AdaptiveRemote.Services.Conversation; internal class FakeSpeechRecognitionEngine : ISpeechRecognitionEngine { - private readonly Dictionary _grammars = new(); + private readonly Dictionary _grammars = new(); private event EventHandler? _recognized; private TaskCompletionSource _pause = new(); @@ -28,8 +27,8 @@ event EventHandler ISpeechRecognitionEngine.SpeechRej remove { } } - void ISpeechRecognitionEngine.LoadGrammar(Grammar grammar) => _grammars.Add(grammar.Name, grammar); - void ISpeechRecognitionEngine.UnloadGrammar(Grammar grammar) => _grammars.Remove(grammar.Name); + void ISpeechRecognitionEngine.LoadGrammar(IGrammar grammar) => _grammars.Add(grammar.Name, grammar); + void ISpeechRecognitionEngine.UnloadGrammar(IGrammar grammar) => _grammars.Remove(grammar.Name); void ISpeechRecognitionEngine.UnloadAllGrammars() => _grammars.Clear(); void ISpeechRecognitionEngine.RecognizeAsync() => _pause.TrySetResult(); void ISpeechRecognitionEngine.RecognizeAsyncCancel() => _pause = new(); @@ -97,7 +96,7 @@ static FakeRecognitionResult Command(string text, string command, int confidence } private bool IsEnabled(PhraseKinds phraseKind) - => _grammars.TryGetValue(phraseKind.ToString(), out Grammar? grammar) && grammar.Enabled; + => _grammars.TryGetValue(phraseKind.ToString(), out IGrammar? grammar) && grammar.Enabled; private void SendResult(FakeRecognitionResult result) { diff --git a/src/AdaptiveRemote/Services/Conversation/GrammarWrapper.cs b/src/AdaptiveRemote/Services/Conversation/GrammarWrapper.cs new file mode 100644 index 0000000..ad3a460 --- /dev/null +++ b/src/AdaptiveRemote/Services/Conversation/GrammarWrapper.cs @@ -0,0 +1,27 @@ +using System.Speech.Recognition; + +namespace AdaptiveRemote.Services.Conversation; + +/// +/// A wrapper around System.Speech.Recognition.Grammar that implements IGrammar +/// +internal class GrammarWrapper : IGrammar +{ + internal GrammarWrapper(Grammar grammar) + { + Grammar = grammar; + } + + /// + /// Gets the underlying System.Speech.Recognition.Grammar + /// + internal Grammar Grammar { get; } + + public string Name => Grammar.Name; + + public bool Enabled + { + get => Grammar.Enabled; + set => Grammar.Enabled = value; + } +} diff --git a/src/AdaptiveRemote/Services/Conversation/IGrammarProvider.cs b/src/AdaptiveRemote/Services/Conversation/IGrammarProvider.cs deleted file mode 100644 index a13a4a5..0000000 --- a/src/AdaptiveRemote/Services/Conversation/IGrammarProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Speech.Recognition; - -namespace AdaptiveRemote.Services.Conversation; - -/// -/// Loader for Grammar objects -/// -internal interface IGrammarProvider -{ - /// - /// Load a grammar that supports the given kind of phrases - /// - Grammar LoadGrammar(PhraseKinds phraseKind); -} diff --git a/src/AdaptiveRemote/Services/Conversation/SpeechRecognitionEngineWrapper.cs b/src/AdaptiveRemote/Services/Conversation/SpeechRecognitionEngineWrapper.cs index 8b89aad..469f9f1 100644 --- a/src/AdaptiveRemote/Services/Conversation/SpeechRecognitionEngineWrapper.cs +++ b/src/AdaptiveRemote/Services/Conversation/SpeechRecognitionEngineWrapper.cs @@ -12,6 +12,7 @@ internal class SpeechRecognitionEngineWrapper : ISpeechRecognitionEngine, IDispo { private readonly SpeechRecognitionEngine _engine = new(new CultureInfo("en-US")); private readonly ILogger _logger; + private readonly Dictionary _grammarMap = new(); private event EventHandler? _speechRecognized; private event EventHandler? _speechRejected; @@ -46,9 +47,34 @@ private void BroadcastSpeechRecognized(object? sender, SpeechRecognizedEventArgs private void BroadcastSpeechRejected(object? sender, SpeechRecognitionRejectedEventArgs e) => _speechRejected?.Invoke(this, new RecognizedSpeechEventArgs(WrapRequired(e.Result))); - public void LoadGrammar(Grammar grammar) => _engine.LoadGrammar(grammar); - public void UnloadGrammar(Grammar grammar) => _engine.UnloadGrammar(grammar); - public void UnloadAllGrammars() => _engine.UnloadAllGrammars(); + public void LoadGrammar(IGrammar grammar) + { + if (grammar is GrammarWrapper wrapper) + { + _grammarMap[grammar] = wrapper.Grammar; + _engine.LoadGrammar(wrapper.Grammar); + } + else + { + throw new ArgumentException($"Expected GrammarWrapper but got {grammar.GetType().Name}", nameof(grammar)); + } + } + + public void UnloadGrammar(IGrammar grammar) + { + if (_grammarMap.TryGetValue(grammar, out var systemGrammar)) + { + _engine.UnloadGrammar(systemGrammar); + _grammarMap.Remove(grammar); + } + } + + public void UnloadAllGrammars() + { + _engine.UnloadAllGrammars(); + _grammarMap.Clear(); + } + public void SetInputToDefaultAudioDevice() => _engine.SetInputToDefaultAudioDevice(); public void RecognizeAsync() => _engine.RecognizeAsync(RecognizeMode.Multiple); public void RecognizeAsyncCancel() => _engine.RecognizeAsyncCancel(); diff --git a/src/AdaptiveRemote/Services/Conversation/StaticGrammarProvider.cs b/src/AdaptiveRemote/Services/Conversation/StaticGrammarProvider.cs index 427af9c..c90d2a8 100644 --- a/src/AdaptiveRemote/Services/Conversation/StaticGrammarProvider.cs +++ b/src/AdaptiveRemote/Services/Conversation/StaticGrammarProvider.cs @@ -9,11 +9,14 @@ internal class StaticGrammarProvider : IGrammarProvider { private static readonly Lazy _grammarFile = new(LoadGrammarFile); - Grammar IGrammarProvider.LoadGrammar(PhraseKinds phraseKind) - => new(_grammarFile.Value, phraseKind.ToString()) + IGrammar IGrammarProvider.LoadGrammar(PhraseKinds phraseKind) + { + var grammar = new Grammar(_grammarFile.Value, phraseKind.ToString()) { Name = phraseKind.ToString() }; + return new GrammarWrapper(grammar); + } private static SrgsDocument LoadGrammarFile() { @@ -23,5 +26,4 @@ private static SrgsDocument LoadGrammarFile() return new SrgsDocument(XmlReader.Create(resourceStream)); } - } diff --git a/src/AdaptiveRemote/Services/Lifecycle/AcceleratedServices.cs b/src/AdaptiveRemote/Services/Lifecycle/AcceleratedServices.cs index b840726..85a127b 100644 --- a/src/AdaptiveRemote/Services/Lifecycle/AcceleratedServices.cs +++ b/src/AdaptiveRemote/Services/Lifecycle/AcceleratedServices.cs @@ -1,58 +1,96 @@ -using AdaptiveRemote.Models; +using AdaptiveRemote.Configuration; +using AdaptiveRemote.Models; +using AdaptiveRemote.Services.Conversation; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace AdaptiveRemote.Services.Lifecycle; -public class AcceleratedServices +/// +/// Windows-specific accelerated services that extends the Core AcceleratedServices +/// with Windows-specific services like MainWindow and Windows speech recognition. +/// +public class WindowsAcceleratedServices { - private IHostBuilder _hostBuilder; + private readonly AcceleratedServices _acceleratedServices; - public LifecycleView ViewModel { get; } - internal ILifecycleViewController Controller { get; } - internal DiagnosticAdapter DiagnosticAdapter { get; } + /// + /// The WPF main window + /// public MainWindow MainWindow { get; } - public AcceleratedServices(string[] args) - { - ViewModel = new(); - Controller = new LifecycleViewController(ViewModel); - DiagnosticAdapter = new(Controller); - - MainWindow = new(ViewModel); + /// + /// The lifecycle view model used to display startup status + /// + public LifecycleView ViewModel => _acceleratedServices.ViewModel; - Controller.SetPhase(LifecyclePhase.Waiting); + /// + /// The lifecycle view controller used to update startup status + /// + public ILifecycleViewController Controller => _acceleratedServices.Controller; - _hostBuilder = Host.CreateDefaultBuilder() - .ConfigureAppSettings(args) - .ConfigureApp() - .ConfigureServices(AddPrecreatedServices); - } + public WindowsAcceleratedServices(string[] args) + { + _acceleratedServices = new AcceleratedServices(args); - private void AddPrecreatedServices(IServiceCollection services) - => services - .AddSingleton(MainWindow) - .AddSingleton(Controller) - .AddSingleton(ViewModel); + MainWindow = new(ViewModel); - public async Task RunApplicationLoopAsync() - { - await Task.Run(async () => - { - try + _acceleratedServices + .ConfigureAppSettings(config => { - IHost host = _hostBuilder.Build(); - await host.RunAsync(); - } - catch (Exception configErrors) + config.AddInMemoryCollection(new Dictionary + { + // This makes the default behavior to publish telemetry when the full application + // is hosted. However, the setting is "false" by default so that test hosting won't + // publish telemetry unless explicitly enabled. + // TODO [ADR-12]: This behavior is currently disabled because we don't have anywhere + // to publish telemetry to. + // ["telemetry:Publish"] = "True" + }); + config.AddUserSecrets(); + config.AddCommandLine(args); + }) + .ConfigureHostServices(services => { - Controller.SetFatalError(configErrors); - throw; - } - finally + // Add Windows-specific pre-created services + services.AddSingleton(MainWindow); + services.AddWpfBlazorWebView(); + services.AddSingleton(); + }) + .ConfigureHostServices((context, services) => { - Controller.SetPhase(LifecyclePhase.CleaningUp); - } - }); + // Add Windows speech services based on configuration + var config = context.Configuration.GetSection(SettingsKeys.Conversation); + bool useFake = config.GetValue(nameof(ConversationSettings.Fake)); + + if (useFake) + { + // Use fake speech recognition for testing without speaking aloud + services + .AddScoped() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } + else + { + services + .AddScoped() + .AddSingleton() + .AddSingleton() + .AddSingleton(); + } + + // Add SamplesRecorder if configured + if (config.GetValue(nameof(ConversationSettings.RecordSamples))) + { + services + .AddHostedService() + .AddSingleton(); + } + }); } + + public Task RunApplicationLoopAsync() => _acceleratedServices.RunApplicationLoopAsync(); } diff --git a/test/AdaptiveRemote.Core.Tests/AdaptiveRemote.Core.Tests.csproj b/test/AdaptiveRemote.Core.Tests/AdaptiveRemote.Core.Tests.csproj new file mode 100644 index 0000000..71050f5 --- /dev/null +++ b/test/AdaptiveRemote.Core.Tests/AdaptiveRemote.Core.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + AdaptiveRemote + + + + + + + + + + + + + + + + + + + + diff --git a/test/AdaptiveRemote.Tests/GlobalSuppressions.cs b/test/AdaptiveRemote.Core.Tests/GlobalSuppressions.cs similarity index 100% rename from test/AdaptiveRemote.Tests/GlobalSuppressions.cs rename to test/AdaptiveRemote.Core.Tests/GlobalSuppressions.cs diff --git a/test/AdaptiveRemote.Tests/GlobalUsings.cs b/test/AdaptiveRemote.Core.Tests/GlobalUsings.cs similarity index 100% rename from test/AdaptiveRemote.Tests/GlobalUsings.cs rename to test/AdaptiveRemote.Core.Tests/GlobalUsings.cs diff --git a/test/AdaptiveRemote.Tests/Services/Broadlink/BroadlinkCommandServiceTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Broadlink/BroadlinkCommandServiceTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Broadlink/BroadlinkCommandServiceTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Broadlink/BroadlinkCommandServiceTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/Broadlink/DeviceLocatorTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Broadlink/DeviceLocatorTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Broadlink/DeviceLocatorTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Broadlink/DeviceLocatorTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/Broadlink/PayloadTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Broadlink/PayloadTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Broadlink/PayloadTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Broadlink/PayloadTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/Broadlink/UdpServiceTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Broadlink/UdpServiceTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Broadlink/UdpServiceTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Broadlink/UdpServiceTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/CommandServiceBaseTests.cs b/test/AdaptiveRemote.Core.Tests/Services/CommandServiceBaseTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/CommandServiceBaseTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/CommandServiceBaseTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/Conversation/ConversationControllerTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Conversation/ConversationControllerTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Conversation/ConversationControllerTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Conversation/ConversationControllerTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/Conversation/ConversationStateExtensionsTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Conversation/ConversationStateExtensionsTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Conversation/ConversationStateExtensionsTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Conversation/ConversationStateExtensionsTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/Conversation/ListeningControllerTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Conversation/ListeningControllerTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Conversation/ListeningControllerTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Conversation/ListeningControllerTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/Conversation/MockOptions.cs b/test/AdaptiveRemote.Core.Tests/Services/Conversation/MockOptions.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Conversation/MockOptions.cs rename to test/AdaptiveRemote.Core.Tests/Services/Conversation/MockOptions.cs diff --git a/test/AdaptiveRemote.Tests/Services/Conversation/SpeechRecognitionTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Conversation/SpeechRecognitionTests.cs similarity index 89% rename from test/AdaptiveRemote.Tests/Services/Conversation/SpeechRecognitionTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Conversation/SpeechRecognitionTests.cs index 2d932b5..594d0d7 100644 --- a/test/AdaptiveRemote.Tests/Services/Conversation/SpeechRecognitionTests.cs +++ b/test/AdaptiveRemote.Core.Tests/Services/Conversation/SpeechRecognitionTests.cs @@ -1,5 +1,4 @@ -using System.Speech.Recognition; -using Moq; +using Moq; namespace AdaptiveRemote.Services.Conversation; @@ -15,15 +14,38 @@ public class SpeechRecognitionTests private readonly Mock MockListenDisposable = new(); private readonly MockOptions MockOptions = new(); - private readonly Grammar MockAttentionGrammar = new(new GrammarBuilder("Attention")) { Name = nameof(MockAttentionGrammar) }; - private readonly Grammar MockCommandsGrammar = new(new GrammarBuilder("Commands")) { Name = nameof(MockCommandsGrammar) }; - private readonly Grammar MockYesNoGrammar = new(new GrammarBuilder("YesNo")) { Name = nameof(MockYesNoGrammar) }; - private readonly Grammar MockCorrectionGrammar = new(new GrammarBuilder("Correction")) { Name = nameof(MockCorrectionGrammar) }; + private readonly MockGrammar MockAttentionGrammar = new(nameof(MockAttentionGrammar)); + private readonly MockGrammar MockCommandsGrammar = new(nameof(MockCommandsGrammar)); + private readonly MockGrammar MockYesNoGrammar = new(nameof(MockYesNoGrammar)); + private readonly MockGrammar MockCorrectionGrammar = new(nameof(MockCorrectionGrammar)); public TestContext? TestContext { get; set; } private ISpeechRecognition CreateSut() => new SpeechRecognition(MockOptions, MockEngine.Object, MockListening.Object, MockGrammarProvider.Object, MockLogger); + /// + /// Helper class to create a mock IGrammar with trackable Enabled state. + /// Uses Moq to create the mock with SetupProperty to track Enabled state changes. + /// + private class MockGrammar + { + private readonly Mock _mock; + + public MockGrammar(string name) + { + _mock = new Mock(); + _mock.Setup(x => x.Name).Returns(name); + _mock.SetupProperty(x => x.Enabled, true); + } + + public IGrammar Object => _mock.Object; + public bool Enabled + { + get => _mock.Object.Enabled; + set => _mock.Object.Enabled = value; + } + } + private static IRecognizedSpeech CreateMockSpeech(params string[] semanticValues) { string? nullValue; @@ -59,46 +81,46 @@ public void SetupMocks() MockAttentionGrammar.Enabled = true; MockGrammarProvider .Setup(x => x.LoadGrammar(PhraseKinds.WakeWord)) - .Returns(MockAttentionGrammar) + .Returns(MockAttentionGrammar.Object) .Verifiable(Times.Once); MockEngine - .Setup(x => x.LoadGrammar(MockAttentionGrammar)) + .Setup(x => x.LoadGrammar(MockAttentionGrammar.Object)) .Callback(() => Assert.IsFalse(MockAttentionGrammar.Enabled, nameof(MockAttentionGrammar) + ".Enabled")) .Verifiable(Times.Once); MockCommandsGrammar.Enabled = true; MockGrammarProvider .Setup(x => x.LoadGrammar(PhraseKinds.Commands)) - .Returns(MockCommandsGrammar) + .Returns(MockCommandsGrammar.Object) .Verifiable(Times.Once); MockEngine - .Setup(x => x.LoadGrammar(MockCommandsGrammar)) + .Setup(x => x.LoadGrammar(MockCommandsGrammar.Object)) .Callback(() => Assert.IsFalse(MockCommandsGrammar.Enabled, nameof(MockCommandsGrammar) + ".Enabled")) .Verifiable(Times.Once); MockYesNoGrammar.Enabled = true; MockGrammarProvider .Setup(x => x.LoadGrammar(PhraseKinds.Confirmation)) - .Returns(MockYesNoGrammar) + .Returns(MockYesNoGrammar.Object) .Verifiable(Times.Once); MockEngine - .Setup(x => x.LoadGrammar(MockYesNoGrammar)) + .Setup(x => x.LoadGrammar(MockYesNoGrammar.Object)) .Callback(() => Assert.IsFalse(MockYesNoGrammar.Enabled, nameof(MockYesNoGrammar) + ".Enabled")) .Verifiable(Times.Once); MockCorrectionGrammar.Enabled = true; MockGrammarProvider .Setup(x => x.LoadGrammar(PhraseKinds.Correction)) - .Returns(MockCorrectionGrammar) + .Returns(MockCorrectionGrammar.Object) .Verifiable(Times.Once); MockEngine - .Setup(x => x.LoadGrammar(MockCorrectionGrammar)) + .Setup(x => x.LoadGrammar(MockCorrectionGrammar.Object)) .Callback(() => Assert.IsFalse(MockCorrectionGrammar.Enabled, nameof(MockCorrectionGrammar) + ".Enabled")) .Verifiable(Times.Once); MockEngine .Setup(x => x.UnloadAllGrammars()) - .Callback(() => MockEngine.Verify(x => x.LoadGrammar(It.IsAny()), Times.Never)) + .Callback(() => MockEngine.Verify(x => x.LoadGrammar(It.IsAny()), Times.Never)) .Verifiable(Times.Once); MockEngine diff --git a/test/AdaptiveRemote.Tests/Services/Conversation/SpeechSynthesisTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Conversation/SpeechSynthesisTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Conversation/SpeechSynthesisTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Conversation/SpeechSynthesisTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/FileSystemExtensionsTests.cs b/test/AdaptiveRemote.Core.Tests/Services/FileSystemExtensionsTests.cs similarity index 53% rename from test/AdaptiveRemote.Tests/Services/FileSystemExtensionsTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/FileSystemExtensionsTests.cs index e9fdc8a..16c7e45 100644 --- a/test/AdaptiveRemote.Tests/Services/FileSystemExtensionsTests.cs +++ b/test/AdaptiveRemote.Core.Tests/Services/FileSystemExtensionsTests.cs @@ -5,6 +5,13 @@ public class FileSystemExtensionsTests { private readonly MockFileSystem MockFileSystem = new(); + // Use a cross-platform root path for tests + private static readonly string Root = OperatingSystem.IsWindows() ? @"C:\" : "/"; + private static readonly string UsersDir = Path.Combine(Root, "users"); + private static readonly string BobDir = Path.Combine(UsersDir, "bob_the_builder"); + private static readonly string TempDir = Path.Combine(BobDir, "temp"); + private static readonly string HatFile = Path.Combine(TempDir, "hat.txt"); + [TestCleanup] public void VerifyMocks() { @@ -15,15 +22,14 @@ public void VerifyMocks() public void FileSystemExtensions_CreateDirectory_Recursive_DoesNothingIfDirectoryExists() { // Arrange - const string input = @"C:\users\bob_the_builder\temp"; - MockFileSystem.AddDirectory(input); + MockFileSystem.AddDirectory(TempDir); MockFileSystem.Expect_CreateDirectory_IsNotCalled(); IFileSystem fileSystem = MockFileSystem.Object; // Act - fileSystem.CreateDirectory(input, recursive: true); + fileSystem.CreateDirectory(TempDir, recursive: true); // Assert } @@ -32,60 +38,52 @@ public void FileSystemExtensions_CreateDirectory_Recursive_DoesNothingIfDirector public void FileSystemExtensions_CreateDirectory_Recursive_CreatesOneLevelOfDirectory() { // Arrange - const string parent = @"C:\users\bob_the_builder"; - const string input = @"C:\users\bob_the_builder\temp"; - MockFileSystem.AddDirectory(parent); + MockFileSystem.AddDirectory(BobDir); IFileSystem fileSystem = MockFileSystem.Object; // Act - fileSystem.CreateDirectory(input, recursive: true); + fileSystem.CreateDirectory(TempDir, recursive: true); // Assert - Assert.IsTrue(fileSystem.DirectoryExists(input), "Directory {0} should have been created", input); + Assert.IsTrue(fileSystem.DirectoryExists(TempDir), "Directory {0} should have been created", TempDir); } [TestMethod] public void FileSystemExtensions_CreateDirectory_Recursive_CreatesMultipleLevelsOfDirectory() { // Arrange - const string parent3 = @"C:\"; - const string parent2 = @"C:\users"; - const string parent1 = @"C:\users\bob_the_builder"; - const string input = @"C:\users\bob_the_builder\temp"; - MockFileSystem.AddDirectory(parent3); + MockFileSystem.AddDirectory(Root); IFileSystem fileSystem = MockFileSystem.Object; // Act - fileSystem.CreateDirectory(input, recursive: true); + fileSystem.CreateDirectory(TempDir, recursive: true); // Assert - Assert.IsTrue(fileSystem.DirectoryExists(input), "Directory {0} should have been created", input); - Assert.IsTrue(fileSystem.DirectoryExists(parent1), "Directory {0} should have been created", parent1); - Assert.IsTrue(fileSystem.DirectoryExists(parent2), "Directory {0} should have been created", parent2); + Assert.IsTrue(fileSystem.DirectoryExists(TempDir), "Directory {0} should have been created", TempDir); + Assert.IsTrue(fileSystem.DirectoryExists(BobDir), "Directory {0} should have been created", BobDir); + Assert.IsTrue(fileSystem.DirectoryExists(UsersDir), "Directory {0} should have been created", UsersDir); } [TestMethod] public void FileSystemExtensions_CreateDirectory_Recursive_ThrowsArgumentExceptionForInvalidPath() { // Arrange - const string input = @"C:\users\bob_the_builder\temp"; - MockFileSystem.Expect_CreateDirectory_IsNotCalled(); - MockFileSystem.Expect_CreateDirectory_ForPath(@"C:\"); + MockFileSystem.Expect_CreateDirectory_ForPath(Root); IFileSystem fileSystem = MockFileSystem.Object; try { // Act - fileSystem.CreateDirectory(input, recursive: true); + fileSystem.CreateDirectory(TempDir, recursive: true); // Assert Assert.Fail("Expected exception was not thrown."); } - catch (AssertFailedException result) when (result.Message == @"Assert.IsNotNull failed. Could not compute the parent path for 'C:\'") + catch (AssertFailedException result) when (result.Message.Contains("Could not compute the parent path for")) { // This is the expected exception } @@ -96,22 +94,20 @@ public void FileSystemExtensions_CreateDirectory_Recursive_ThrowsArgumentExcepti public void FileSystemExtensions_CreateDirectory_NotRecursive_ThrowsArgumentExceptionForInvalidPath() { // Arrange - const string input = @"C:\users\bob_the_builder\temp"; - MockFileSystem.Expect_CreateDirectory_IsNotCalled(); - MockFileSystem.Expect_CreateDirectory_ForPath(input); + MockFileSystem.Expect_CreateDirectory_ForPath(TempDir); IFileSystem fileSystem = MockFileSystem.Object; try { // Act - fileSystem.CreateDirectory(input, recursive: false); + fileSystem.CreateDirectory(TempDir, recursive: false); // Assert Assert.Fail("Expected exception was not thrown."); } - catch (AssertFailedException result) when (result.Message == @"Assert.IsTrue failed. Parent path 'C:\users\bob_the_builder' does not exist when attempting to create 'C:\users\bob_the_builder\temp'") + catch (AssertFailedException result) when (result.Message.Contains("does not exist when attempting to create")) { // This is the expected exception } @@ -121,16 +117,14 @@ public void FileSystemExtensions_CreateDirectory_NotRecursive_ThrowsArgumentExce public void FileSystemExtensions_OpenWrite_CreateDirectory_DoesNotCreateDirectoryThatAlreadyExists() { // Arrange - const string input = @"C:\users\bob_the_builder\temp\hat.txt"; - - MockFileSystem.AddDirectory(@"C:\users\bob_the_builder\temp"); + MockFileSystem.AddDirectory(TempDir); MockFileSystem.Expect_CreateDirectory_IsNotCalled(); - MockFileSystem.Expect_OpenWrite_ForPath(input); + MockFileSystem.Expect_OpenWrite_ForPath(HatFile); IFileSystem fileSystem = MockFileSystem.Object; // Act - Stream resultStream = fileSystem.OpenWrite(input, createDirectory: true); + Stream resultStream = fileSystem.OpenWrite(HatFile, createDirectory: true); // Assert Assert.IsNotNull(resultStream, nameof(resultStream)); @@ -140,30 +134,26 @@ public void FileSystemExtensions_OpenWrite_CreateDirectory_DoesNotCreateDirector public void FileSystemExtensions_OpenWrite_CreateDirectory_CreatesDirectoryThatDoesNotExist() { // Arrange - const string input = @"C:\users\bob_the_builder\temp\hat.txt"; - - MockFileSystem.AddDirectory(@"C:\users"); - MockFileSystem.Expect_OpenWrite_ForPath(input); + MockFileSystem.AddDirectory(UsersDir); + MockFileSystem.Expect_OpenWrite_ForPath(HatFile); IFileSystem fileSystem = MockFileSystem.Object; // Act - Stream resultStream = fileSystem.OpenWrite(input, createDirectory: true); + Stream resultStream = fileSystem.OpenWrite(HatFile, createDirectory: true); // Assert Assert.IsNotNull(resultStream, nameof(resultStream)); - Assert.IsTrue(fileSystem.DirectoryExists(@"C:\users\bob_the_builder"), "bob_the_builder does not exit"); - Assert.IsTrue(fileSystem.DirectoryExists(@"C:\users\bob_the_builder\temp"), "temp does not exist"); + Assert.IsTrue(fileSystem.DirectoryExists(BobDir), "bob_the_builder does not exit"); + Assert.IsTrue(fileSystem.DirectoryExists(TempDir), "temp does not exist"); } [TestMethod] public void FileSystemExtensions_OpenWrite_DoNotCreateDirectory_ThrowsForDirectoryNotFound() { // Arrange - const string input = @"C:\users\bob_the_builder\temp\hat.txt"; - - MockFileSystem.AddDirectory(@"C:\users"); - MockFileSystem.Expect_OpenWrite_ForPath(input); + MockFileSystem.AddDirectory(UsersDir); + MockFileSystem.Expect_OpenWrite_ForPath(HatFile); MockFileSystem.Expect_CreateDirectory_IsNotCalled(); IFileSystem fileSystem = MockFileSystem.Object; @@ -171,12 +161,12 @@ public void FileSystemExtensions_OpenWrite_DoNotCreateDirectory_ThrowsForDirecto try { // Act - Stream resultStream = fileSystem.OpenWrite(input, createDirectory: false); + Stream resultStream = fileSystem.OpenWrite(HatFile, createDirectory: false); // Assert Assert.Fail("Expected exception was not thrown"); } - catch (AssertFailedException result) when (result.Message == "Assert.IsTrue failed. Attempted to open a file for writing in directory that does not exist: C:\\users\\bob_the_builder\\temp\\hat.txt") + catch (AssertFailedException result) when (result.Message.Contains("Attempted to open a file for writing in directory that does not exist")) { // This is the expected exception } diff --git a/test/AdaptiveRemote.Tests/Services/Lifecycle/ApplicationLifecycleTests.cs b/test/AdaptiveRemote.Core.Tests/Services/Lifecycle/ApplicationLifecycleTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/Lifecycle/ApplicationLifecycleTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/Lifecycle/ApplicationLifecycleTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/PersistSettingsExtensionsTests.cs b/test/AdaptiveRemote.Core.Tests/Services/PersistSettingsExtensionsTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/PersistSettingsExtensionsTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/PersistSettingsExtensionsTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/ProgrammaticSettings/PersistSettingsTests.cs b/test/AdaptiveRemote.Core.Tests/Services/ProgrammaticSettings/PersistSettingsTests.cs similarity index 91% rename from test/AdaptiveRemote.Tests/Services/ProgrammaticSettings/PersistSettingsTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/ProgrammaticSettings/PersistSettingsTests.cs index da81e46..96da74e 100644 --- a/test/AdaptiveRemote.Tests/Services/ProgrammaticSettings/PersistSettingsTests.cs +++ b/test/AdaptiveRemote.Core.Tests/Services/ProgrammaticSettings/PersistSettingsTests.cs @@ -6,14 +6,24 @@ namespace AdaptiveRemote.Services.ProgrammaticSettings; [TestClass] public class PersistSettingsTests { - private const string InputSettingsPath = @"C:\path\to\settings.ini"; + // Use cross-platform paths + private static readonly string Root = OperatingSystem.IsWindows() ? @"C:\" : "/"; + private static readonly string PathDir = Path.Combine(Root, "path"); + private static readonly string ToDir = Path.Combine(PathDir, "to"); + private static readonly string InputSettingsPath = Path.Combine(ToDir, "settings.ini"); + private static readonly string NewLine = Environment.NewLine; private readonly MockLogger MockLogger = new(); private readonly MockFileSystem MockFileSystem = new(); - private readonly MockOptions MockOptions = new(new() + private readonly MockOptions MockOptions; + + public PersistSettingsTests() { - ProgrammaticSettingsPath = InputSettingsPath - }); + MockOptions = new MockOptions(new() + { + ProgrammaticSettingsPath = InputSettingsPath + }); + } private PersistSettings CreateSut() => new(MockFileSystem.Object, MockOptions, MockLogger); @@ -48,7 +58,7 @@ public async Task PersistSettings_Set_SavesSettingsToFile() await MockLogger.WaitForMessage(ExpectMessage_SavedSettings()); // Assert - MockFileSystem.VerifyFileContents(InputSettingsPath, "ExistingSetting=123\r\nNewSetting=abc\r\n"); + MockFileSystem.VerifyFileContents(InputSettingsPath, $"ExistingSetting=123{NewLine}NewSetting=abc{NewLine}"); MockLogger.VerifyMessages( ExpectMessage_LoadingExistingSettings(), @@ -113,7 +123,7 @@ public async Task PersistSettings_Set_ChangesExistingSettingInFile() await MockLogger.WaitForMessage(ExpectMessage_SavedSettings()); // Assert - MockFileSystem.VerifyFileContents(InputSettingsPath, "ExistingSetting=ghi\r\n"); + MockFileSystem.VerifyFileContents(InputSettingsPath, $"ExistingSetting=ghi{NewLine}"); MockLogger.VerifyMessages( ExpectMessage_LoadingExistingSettings(), @@ -195,7 +205,7 @@ public async Task PersistSettings_Set_WhenFileNotFound_CreatesFile() await MockLogger.WaitForMessage(ExpectMessage_SavedSettings()); // Assert - MockFileSystem.VerifyFileContents(InputSettingsPath, "NewSetting=abc\r\n"); + MockFileSystem.VerifyFileContents(InputSettingsPath, $"NewSetting=abc{NewLine}"); MockLogger.VerifyMessages( ExpectMessage_AddSetting("NewSetting", "abc"), @@ -209,12 +219,12 @@ public async Task PersistSettings_Set_WhenFileNotFound_CreatesDirectory() // Arrange IPersistSettings sut = CreateSut(); - MockFileSystem.AddDirectory(Path.GetPathRoot(InputSettingsPath)); + MockFileSystem.AddDirectory(Root); MockFileSystem.Expect_OpenRead_IsNotCalled(); - MockFileSystem.Expect_CreateDirectory_ForPath(@"C:\path"); - MockFileSystem.Expect_CreateDirectory_ForPath(@"C:\path\to"); + MockFileSystem.Expect_CreateDirectory_ForPath(PathDir); + MockFileSystem.Expect_CreateDirectory_ForPath(ToDir); MockFileSystem.Expect_OpenWrite_ForPath(InputSettingsPath); @@ -224,7 +234,7 @@ public async Task PersistSettings_Set_WhenFileNotFound_CreatesDirectory() await MockLogger.WaitForMessage(ExpectMessage_SavedSettings()); // Assert - MockFileSystem.VerifyFileContents(InputSettingsPath, "NewSetting=abc\r\n"); + MockFileSystem.VerifyFileContents(InputSettingsPath, $"NewSetting=abc{NewLine}"); MockLogger.VerifyMessages( ExpectMessage_AddSetting("NewSetting", "abc"), diff --git a/test/AdaptiveRemote.Tests/Services/ScopedBackgroundProcessTests.cs b/test/AdaptiveRemote.Core.Tests/Services/ScopedBackgroundProcessTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/ScopedBackgroundProcessTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/ScopedBackgroundProcessTests.cs diff --git a/test/AdaptiveRemote.Tests/Services/TiVo/TiVoServiceTests.cs b/test/AdaptiveRemote.Core.Tests/Services/TiVo/TiVoServiceTests.cs similarity index 100% rename from test/AdaptiveRemote.Tests/Services/TiVo/TiVoServiceTests.cs rename to test/AdaptiveRemote.Core.Tests/Services/TiVo/TiVoServiceTests.cs diff --git a/test/AdaptiveRemote.Tests/TestUtilities/MemoryAssert.cs b/test/AdaptiveRemote.Core.Tests/TestUtilities/MemoryAssert.cs similarity index 100% rename from test/AdaptiveRemote.Tests/TestUtilities/MemoryAssert.cs rename to test/AdaptiveRemote.Core.Tests/TestUtilities/MemoryAssert.cs diff --git a/test/AdaptiveRemote.Tests/TestUtilities/MockEndPoint.cs b/test/AdaptiveRemote.Core.Tests/TestUtilities/MockEndPoint.cs similarity index 100% rename from test/AdaptiveRemote.Tests/TestUtilities/MockEndPoint.cs rename to test/AdaptiveRemote.Core.Tests/TestUtilities/MockEndPoint.cs diff --git a/test/AdaptiveRemote.Tests/TestUtilities/MockExtensions.cs b/test/AdaptiveRemote.Core.Tests/TestUtilities/MockExtensions.cs similarity index 100% rename from test/AdaptiveRemote.Tests/TestUtilities/MockExtensions.cs rename to test/AdaptiveRemote.Core.Tests/TestUtilities/MockExtensions.cs diff --git a/test/AdaptiveRemote.Tests/TestUtilities/MockFileSystem.cs b/test/AdaptiveRemote.Core.Tests/TestUtilities/MockFileSystem.cs similarity index 100% rename from test/AdaptiveRemote.Tests/TestUtilities/MockFileSystem.cs rename to test/AdaptiveRemote.Core.Tests/TestUtilities/MockFileSystem.cs diff --git a/test/AdaptiveRemote.Tests/TestUtilities/MockLogger.cs b/test/AdaptiveRemote.Core.Tests/TestUtilities/MockLogger.cs similarity index 100% rename from test/AdaptiveRemote.Tests/TestUtilities/MockLogger.cs rename to test/AdaptiveRemote.Core.Tests/TestUtilities/MockLogger.cs diff --git a/test/AdaptiveRemote.Tests/TestUtilities/MockOptions.cs b/test/AdaptiveRemote.Core.Tests/TestUtilities/MockOptions.cs similarity index 100% rename from test/AdaptiveRemote.Tests/TestUtilities/MockOptions.cs rename to test/AdaptiveRemote.Core.Tests/TestUtilities/MockOptions.cs diff --git a/test/AdaptiveRemote.Tests/TestUtilities/StringExtensions.cs b/test/AdaptiveRemote.Core.Tests/TestUtilities/StringExtensions.cs similarity index 100% rename from test/AdaptiveRemote.Tests/TestUtilities/StringExtensions.cs rename to test/AdaptiveRemote.Core.Tests/TestUtilities/StringExtensions.cs diff --git a/test/AdaptiveRemote.Tests/TestUtilities/TaskAssert.cs b/test/AdaptiveRemote.Core.Tests/TestUtilities/TaskAssert.cs similarity index 100% rename from test/AdaptiveRemote.Tests/TestUtilities/TaskAssert.cs rename to test/AdaptiveRemote.Core.Tests/TestUtilities/TaskAssert.cs diff --git a/test/AdaptiveRemote.Tests/AdaptiveRemote.Tests.csproj b/test/AdaptiveRemote.Tests/AdaptiveRemote.Tests.csproj index a4e842e..a1a8033 100644 --- a/test/AdaptiveRemote.Tests/AdaptiveRemote.Tests.csproj +++ b/test/AdaptiveRemote.Tests/AdaptiveRemote.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0-windows @@ -7,29 +7,22 @@ false true - AdaptiveRemote - - - - - + + + - - - - - + - + diff --git a/test/AdaptiveRemote.Tests/Services/Conversation/SpeechSamples/Samples.txt b/test/AdaptiveRemote.Tests/Services/Conversation/SpeechSamples/Samples.txt deleted file mode 100644 index dd55da3..0000000 --- a/test/AdaptiveRemote.Tests/Services/Conversation/SpeechSamples/Samples.txt +++ /dev/null @@ -1,58 +0,0 @@ -Advance => "Sent Skip" -Back => "Sent Back" -Channel down => "Sent Channel down" -Channel up => "Sent Channel up" -Down => "Sent Down" -Exit => "Sent Exit" -Go back => "Sent Back" -Go down => "Sent Down" -Go left => "Sent Left" -Go right => "Sent Right" -Go to Guide => "Sent Guide" -Go to Netflix => "Sent Netflix" -Go to TiVo => "Sent TiVo" -Go up => "Sent Up" -Guide => "Sent Guide" -Left => "Sent Left" -Louder => "Sent Volume up" -Netflix => "Sent Netflix" -OK => "Sent Select" -Page down => "Sent Channel down" -Page up => "Sent Channel up" -Pause => "Sent Pause" -Play => "Sent Play" -Quieter => "Sent Volume down" -Quit => "Sent Exit" -Record => "Sent Record" -Replay => "Sent Replay" -Right => "Sent Right" -Select => "Sent Select" -Skip => "Sent Skip" -Skip back => "Sent Replay" -Skip forward => "Sent Skip" -Softer => "Sent Volumn down" -TiVo => "Sent TiVo" -Up => "Sent Up" -Volume down => "Sent Volume down" -Volume up => "Sent Volume up" -Stop listening => "OK" - -Go up once => "Sent Up" -Go down one => "Sent Down" -Go left one time => "Sent Left" -Go right twice => "Sent Right twice" -Volume down two => "Sent Volume down twice" -Channel up two times => "Sent Clannel up twice" -Channel down three => "Sent Channel down three times" -Replay three times => "Sent Replay three times" -Skip back four => "Sent Back four times" -Skip four times => "Sent Skip four times" -Skip forward five => "Sent Skip five times" -Page up six => "Sent Channel up six times" -Page down six times => "Sent Channel down six times" -Volume up seven => "Sent Volume up seven times" -Up seven times => "Sent Up seven times" -Down eight => "Sent Down eight times" -Left eight times => "Sent Left eight times" -Right nine times => "Sent Right nine times" -Go back nine => "Sent Back nine times" \ No newline at end of file