Public Access
Make meeting lifecycle stateful
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace MeetingAssistant.LaunchProfiles;
|
||||
|
||||
public interface ILaunchProfileOptionsProvider
|
||||
{
|
||||
LaunchProfile GetRequiredProfile(string? name);
|
||||
|
||||
IReadOnlyList<LaunchProfileHotkey> GetHotkeys();
|
||||
}
|
||||
|
||||
public sealed record LaunchProfile(string Name, MeetingAssistantOptions Options);
|
||||
|
||||
public sealed record LaunchProfileHotkey(string ProfileName, string Toggle);
|
||||
|
||||
public sealed class ConfigurationLaunchProfileOptionsProvider : ILaunchProfileOptionsProvider
|
||||
{
|
||||
public const string DefaultProfileName = "default";
|
||||
|
||||
private readonly IConfiguration configuration;
|
||||
|
||||
public ConfigurationLaunchProfileOptionsProvider(IConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
public LaunchProfile GetRequiredProfile(string? name)
|
||||
{
|
||||
var profileName = NormalizeProfileName(name);
|
||||
var options = BindDefaultOptions();
|
||||
if (profileName.Equals(DefaultProfileName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new LaunchProfile(DefaultProfileName, options);
|
||||
}
|
||||
|
||||
var profileSection = GetProfilesSection().GetSection(profileName);
|
||||
if (!profileSection.Exists())
|
||||
{
|
||||
throw new KeyNotFoundException($"Launch profile '{profileName}' is not configured.");
|
||||
}
|
||||
|
||||
profileSection.Bind(options);
|
||||
ApplyArrayOverrides(profileSection, options);
|
||||
return new LaunchProfile(profileName, options);
|
||||
}
|
||||
|
||||
public IReadOnlyList<LaunchProfileHotkey> GetHotkeys()
|
||||
{
|
||||
var hotkeys = GetProfileNames()
|
||||
.Select(profileName =>
|
||||
{
|
||||
var profile = GetRequiredProfile(profileName);
|
||||
return new LaunchProfileHotkey(profile.Name, profile.Options.Hotkey.Toggle);
|
||||
})
|
||||
.Where(hotkey => !string.IsNullOrWhiteSpace(hotkey.Toggle))
|
||||
.ToList();
|
||||
var duplicate = hotkeys
|
||||
.GroupBy(hotkey => hotkey.Toggle.Trim(), StringComparer.OrdinalIgnoreCase)
|
||||
.FirstOrDefault(group => group.Count() > 1);
|
||||
if (duplicate is not null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Launch profile hotkey '{duplicate.Key}' is configured more than once for profiles {string.Join(", ", duplicate.Select(hotkey => hotkey.ProfileName))}.");
|
||||
}
|
||||
|
||||
return hotkeys;
|
||||
}
|
||||
|
||||
private IReadOnlyList<string> GetProfileNames()
|
||||
{
|
||||
return new[] { DefaultProfileName }
|
||||
.Concat(GetProfilesSection()
|
||||
.GetChildren()
|
||||
.Select(section => section.Key)
|
||||
.Where(key => !key.Equals(DefaultProfileName, StringComparison.OrdinalIgnoreCase)))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private MeetingAssistantOptions BindDefaultOptions()
|
||||
{
|
||||
var options = new MeetingAssistantOptions();
|
||||
GetRootSection().Bind(options);
|
||||
return options;
|
||||
}
|
||||
|
||||
private IConfigurationSection GetRootSection()
|
||||
{
|
||||
return configuration.GetSection("MeetingAssistant");
|
||||
}
|
||||
|
||||
private IConfigurationSection GetProfilesSection()
|
||||
{
|
||||
return GetRootSection().GetSection("LaunchProfiles");
|
||||
}
|
||||
|
||||
private static string NormalizeProfileName(string? name)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(name) ||
|
||||
name.Equals(DefaultProfileName, StringComparison.OrdinalIgnoreCase)
|
||||
? DefaultProfileName
|
||||
: name.Trim();
|
||||
}
|
||||
|
||||
private static void ApplyArrayOverrides(
|
||||
IConfigurationSection profileSection,
|
||||
MeetingAssistantOptions options)
|
||||
{
|
||||
var autoDetectLanguages = ReadArrayOverride(profileSection.GetSection("AzureSpeech:AutoDetectLanguages"));
|
||||
if (autoDetectLanguages is not null)
|
||||
{
|
||||
options.AzureSpeech.AutoDetectLanguages = autoDetectLanguages;
|
||||
}
|
||||
|
||||
var funAsrChunkSize = ReadIntArrayOverride(profileSection.GetSection("FunAsr:ChunkSize"));
|
||||
if (funAsrChunkSize is not null)
|
||||
{
|
||||
options.FunAsr.ChunkSize = funAsrChunkSize;
|
||||
}
|
||||
}
|
||||
|
||||
private static string[]? ReadArrayOverride(IConfigurationSection section)
|
||||
{
|
||||
if (!section.Exists())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return section
|
||||
.GetChildren()
|
||||
.OrderBy(child => int.TryParse(child.Key, out var index) ? index : int.MaxValue)
|
||||
.Select(child => child.Value)
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||
.Select(value => value!)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static int[]? ReadIntArrayOverride(IConfigurationSection section)
|
||||
{
|
||||
if (!section.Exists())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return section
|
||||
.GetChildren()
|
||||
.OrderBy(child => int.TryParse(child.Key, out var index) ? index : int.MaxValue)
|
||||
.Select(child => int.TryParse(child.Value, out var value) ? value : (int?)null)
|
||||
.Where(value => value.HasValue)
|
||||
.Select(value => value!.Value)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user