Files

206 lines
6.7 KiB
C#

using Microsoft.Extensions.Configuration;
namespace MeetingAssistant.LaunchProfiles;
public interface ILaunchProfileOptionsProvider
{
LaunchProfile GetRequiredProfile(string? name);
IReadOnlyList<LaunchProfile> GetProfiles();
IReadOnlyList<LaunchProfileHotkey> GetHotkeys();
}
public sealed record LaunchProfile(string Name, MeetingAssistantOptions Options);
public enum LaunchProfileHotkeyAction
{
ToggleRecording,
AbortRecording,
CaptureScreenshot
}
public sealed record LaunchProfileHotkey(
string ProfileName,
string Hotkey,
LaunchProfileHotkeyAction Action);
public sealed class ConfigurationLaunchProfileOptionsProvider : ILaunchProfileOptionsProvider
{
public const string DefaultProfileName = "default";
private static readonly HotkeyDescriptor[] HotkeyDescriptors =
[
new("Hotkey:Toggle", options => options.Hotkey.Toggle, LaunchProfileHotkeyAction.ToggleRecording),
new("Hotkey:Abort", options => options.Hotkey.Abort, LaunchProfileHotkeyAction.AbortRecording),
new("Screenshots:Hotkey", options => options.Screenshots.Hotkey, LaunchProfileHotkeyAction.CaptureScreenshot)
];
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()
.SelectMany(CreateHotkeys)
.ToList();
var duplicate = hotkeys
.GroupBy(hotkey => hotkey.Hotkey.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}:{hotkey.Action}"))}.");
}
return hotkeys;
}
public IReadOnlyList<LaunchProfile> GetProfiles()
{
return GetProfileNames()
.Select(GetRequiredProfile)
.ToList();
}
private IEnumerable<LaunchProfileHotkey> CreateHotkeys(string profileName)
{
var profile = GetRequiredProfile(profileName);
var isDefaultProfile = profileName.Equals(DefaultProfileName, StringComparison.OrdinalIgnoreCase);
var profileSection = isDefaultProfile
? null
: GetProfilesSection().GetSection(profileName);
foreach (var descriptor in HotkeyDescriptors)
{
var hotkey = descriptor.GetValue(profile.Options);
if (ShouldRegisterHotkey(isDefaultProfile, profileSection?.GetSection(descriptor.SectionPath)) &&
!string.IsNullOrWhiteSpace(hotkey))
{
yield return new LaunchProfileHotkey(
profile.Name,
hotkey,
descriptor.Action);
}
}
}
private static bool ShouldRegisterHotkey(bool isDefaultProfile, IConfigurationSection? profileHotkeySection)
{
return isDefaultProfile || profileHotkeySection?.Exists() == true;
}
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();
}
private sealed record HotkeyDescriptor(
string SectionPath,
Func<MeetingAssistantOptions, string> GetValue,
LaunchProfileHotkeyAction Action);
}