Public Access
206 lines
6.7 KiB
C#
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);
|
|
}
|