Files
MBN_STOCK_WEBVIEW/src/MBN_STOCK_WEBVIEW.Playout/Configuration/PlayoutOptionsLoader.cs

265 lines
8.9 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
using MBN_STOCK_WEBVIEW.Core.Playout;
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
namespace MBN_STOCK_WEBVIEW.Playout.Configuration;
public static class PlayoutOptionsLoader
{
public const string LiveAuthorizationEnvironmentVariable =
"MBN_STOCK_PLAYOUT_AUTHORIZE_LIVE_OUTPUT";
public const string RequiredLiveAuthorizationValue =
"I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH";
public static string DefaultPath => Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MBN_STOCK_WEBVIEW",
"Config",
"playout.local.json");
public static PlayoutOptions Load(string? path = null)
{
var options = LoadJson(path ?? DefaultPath);
ApplyEnvironment(options);
ApplyExecutableAssetDefaults(options, AppContext.BaseDirectory);
return options;
}
/// <summary>
/// Loads only the specified JSON file and deliberately ignores all environment
/// overrides. Intended for explicit, reproducible diagnostic commands.
/// </summary>
public static PlayoutOptions LoadFileOnly(string path)
{
ArgumentException.ThrowIfNullOrWhiteSpace(path);
return LoadJson(path);
}
internal static bool IsLiveAuthorizedForThisLaunch() =>
string.Equals(
Environment.GetEnvironmentVariable(LiveAuthorizationEnvironmentVariable),
RequiredLiveAuthorizationValue,
StringComparison.Ordinal);
internal static void ApplyExecutableAssetDefaults(
PlayoutOptions options,
string? baseDirectory)
{
ArgumentNullException.ThrowIfNull(options);
if (!string.IsNullOrWhiteSpace(options.SceneDirectory) ||
string.IsNullOrWhiteSpace(baseDirectory))
{
return;
}
try
{
var cutsDirectory = Path.Combine(
Path.GetFullPath(baseDirectory),
"Cuts");
if (Directory.Exists(cutsDirectory))
{
options.SceneDirectory = cutsDirectory;
}
}
catch (Exception exception) when (
exception is ArgumentException or IOException or NotSupportedException or
System.Security.SecurityException)
{
// A missing or invalid executable asset root leaves the existing safe
// configuration unchanged; validation reports it if a command needs it.
}
}
private static PlayoutOptions LoadJson(string path)
{
if (!File.Exists(path))
{
return new PlayoutOptions();
}
try
{
var json = File.ReadAllText(path);
return JsonSerializer.Deserialize<PlayoutOptions>(json, JsonOptions())
?? new PlayoutOptions();
}
catch (JsonException exception)
{
throw new PlayoutConfigurationException(
"송출 설정 파일 형식이 올바르지 않습니다.", exception);
}
catch (IOException exception)
{
throw new PlayoutConfigurationException(
"송출 설정 파일을 읽을 수 없습니다.", exception);
}
catch (UnauthorizedAccessException exception)
{
throw new PlayoutConfigurationException(
"송출 설정 파일에 접근할 수 없습니다.", exception);
}
}
private static void ApplyEnvironment(PlayoutOptions options)
{
ApplyEnum(
"MBN_STOCK_PLAYOUT_MODE",
(PlayoutMode value) => options.Mode = value);
ApplyString("MBN_STOCK_PLAYOUT_HOST", value => options.Host = value);
ApplyInt("MBN_STOCK_PLAYOUT_PORT", value => options.Port = value);
ApplyInt("MBN_STOCK_PLAYOUT_TCP_MODE", value => options.TcpMode = value);
ApplyInt("MBN_STOCK_PLAYOUT_CLIENT_PORT", value => options.ClientPort = value);
ApplyNullableInt("MBN_STOCK_PLAYOUT_OUTPUT_CHANNEL", value => options.OutputChannel = value);
ApplyInt("MBN_STOCK_PLAYOUT_LAYOUT_INDEX", value => options.LayoutIndex = value);
ApplyInt(
"MBN_STOCK_PLAYOUT_LEGACY_FADE_DURATION",
value => options.LegacySceneFadeDuration = value);
ApplyEnum(
"MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_KIND",
(LegacySceneBackgroundKind value) => options.LegacySceneBackgroundKind = value);
ApplyString(
"MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_ASSET",
value => options.LegacySceneBackgroundAssetPath = value);
ApplyInt(
"MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_VIDEO_LOOP_COUNT",
value => options.LegacySceneBackgroundVideoLoopCount = value);
ApplyBool(
"MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_VIDEO_LOOP_INFINITE",
value => options.LegacySceneBackgroundVideoLoopInfinite = value);
ApplyString(
"MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_DIRECTORY",
value => options.LegacyBackgroundDirectory = value);
ApplyString("MBN_STOCK_PLAYOUT_SCENE_DIRECTORY", value => options.SceneDirectory = value);
ApplyString(
"MBN_STOCK_PLAYOUT_TEST_WINDOW_TITLE_PATTERN",
value => options.TestProcessWindowTitlePattern = value);
ApplyInt("MBN_STOCK_PLAYOUT_QUEUE_CAPACITY", value => options.QueueCapacity = value);
ApplyInt(
"MBN_STOCK_PLAYOUT_CONNECT_TIMEOUT_MS",
value => options.ConnectTimeoutMilliseconds = value);
ApplyInt(
"MBN_STOCK_PLAYOUT_OPERATION_TIMEOUT_MS",
value => options.OperationTimeoutMilliseconds = value);
ApplyInt(
"MBN_STOCK_PLAYOUT_DISCONNECT_TIMEOUT_MS",
value => options.DisconnectTimeoutMilliseconds = value);
ApplyInt(
"MBN_STOCK_PLAYOUT_PROCESS_POLL_INTERVAL_MS",
value => options.ProcessPollIntervalMilliseconds = value);
ApplyInt(
"MBN_STOCK_PLAYOUT_RECONNECT_DELAY_MS",
value => options.ReconnectDelayMilliseconds = value);
ApplyInt(
"MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS",
value => options.MaximumReconnectAttempts = value);
ApplyBool(
"MBN_STOCK_PLAYOUT_RECONNECT_ENABLED",
value => options.ReconnectEnabled = value);
}
private static JsonSerializerOptions JsonOptions()
{
var result = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true
};
result.Converters.Add(new JsonStringEnumConverter());
return result;
}
private static void ApplyString(string name, Action<string> apply)
{
var value = Environment.GetEnvironmentVariable(name);
if (!string.IsNullOrWhiteSpace(value))
{
apply(value.Trim());
}
}
private static void ApplyInt(string name, Action<int> apply)
{
var text = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrWhiteSpace(text))
{
return;
}
if (!int.TryParse(text, out var value))
{
throw InvalidEnvironment(name);
}
apply(value);
}
private static void ApplyNullableInt(string name, Action<int?> apply)
{
var text = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrWhiteSpace(text))
{
return;
}
if (!int.TryParse(text, out var value))
{
throw InvalidEnvironment(name);
}
apply(value);
}
private static void ApplyBool(string name, Action<bool> apply)
{
var text = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrWhiteSpace(text))
{
return;
}
if (!bool.TryParse(text, out var value))
{
throw InvalidEnvironment(name);
}
apply(value);
}
private static void ApplyEnum<TEnum>(string name, Action<TEnum> apply)
where TEnum : struct, Enum
{
var text = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrWhiteSpace(text))
{
return;
}
if (!Enum.TryParse<TEnum>(text, true, out var value) || !Enum.IsDefined(value))
{
throw InvalidEnvironment(name);
}
apply(value);
}
private static PlayoutConfigurationException InvalidEnvironment(string name) =>
new($"환경 변수 {name} 값이 올바르지 않습니다.");
}
public sealed class PlayoutConfigurationException : Exception
{
public PlayoutConfigurationException(string message)
: base(message)
{
}
public PlayoutConfigurationException(string message, Exception innerException)
: base(message, innerException)
{
}
}