feat: initialize existing development playout PCs
This commit is contained in:
@@ -57,6 +57,14 @@ internal static class DevelopmentLiveLaunchBootstrap
|
||||
InteropSha256Property
|
||||
};
|
||||
|
||||
private static readonly string[] ArmedEnvironmentNames =
|
||||
[
|
||||
InstalledK3dInteropMetadata.ApprovedNativeSha256EnvironmentVariable,
|
||||
InstalledK3dInteropMetadata.ApprovedSha256EnvironmentVariable,
|
||||
ModeEnvironmentVariable,
|
||||
PlayoutOptionsLoader.LiveAuthorizationEnvironmentVariable
|
||||
];
|
||||
|
||||
internal static string DefaultPath => Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"MBN_STOCK_WEBVIEW",
|
||||
@@ -119,7 +127,9 @@ internal static class DevelopmentLiveLaunchBootstrap
|
||||
|
||||
if (string.IsNullOrWhiteSpace(authorizationFilePath))
|
||||
{
|
||||
return Rejected("development-live-authorization-unavailable");
|
||||
return RejectedAndDisarm(
|
||||
platform,
|
||||
"development-live-authorization-unavailable");
|
||||
}
|
||||
|
||||
byte[] json;
|
||||
@@ -131,19 +141,25 @@ internal static class DevelopmentLiveLaunchBootstrap
|
||||
}
|
||||
catch (Exception exception) when (IsExpectedFileFailure(exception))
|
||||
{
|
||||
return Rejected("development-live-authorization-unavailable");
|
||||
return RejectedAndDisarm(
|
||||
platform,
|
||||
"development-live-authorization-unavailable");
|
||||
}
|
||||
|
||||
if (!TryParseAuthorization(json, out var nativeSha256, out var interopSha256))
|
||||
{
|
||||
return Rejected("development-live-authorization-invalid");
|
||||
return RejectedAndDisarm(
|
||||
platform,
|
||||
"development-live-authorization-invalid");
|
||||
}
|
||||
|
||||
return TryApplyEnvironment(platform, nativeSha256, interopSha256)
|
||||
? new DevelopmentLiveBootstrapResult(
|
||||
DevelopmentLiveBootstrapStatus.Applied,
|
||||
"development-live-applied")
|
||||
: Rejected("development-live-environment-failed");
|
||||
: RejectedAndDisarm(
|
||||
platform,
|
||||
"development-live-environment-failed");
|
||||
}
|
||||
|
||||
private static bool TryParseAuthorization(
|
||||
@@ -240,17 +256,10 @@ internal static class DevelopmentLiveLaunchBootstrap
|
||||
string nativeSha256,
|
||||
string interopSha256)
|
||||
{
|
||||
string[] names =
|
||||
[
|
||||
InstalledK3dInteropMetadata.ApprovedNativeSha256EnvironmentVariable,
|
||||
InstalledK3dInteropMetadata.ApprovedSha256EnvironmentVariable,
|
||||
ModeEnvironmentVariable,
|
||||
PlayoutOptionsLoader.LiveAuthorizationEnvironmentVariable
|
||||
];
|
||||
var originals = new Dictionary<string, string?>(StringComparer.Ordinal);
|
||||
try
|
||||
{
|
||||
foreach (var name in names)
|
||||
foreach (var name in ArmedEnvironmentNames)
|
||||
{
|
||||
originals.Add(name, platform.GetProcessEnvironmentVariable(name));
|
||||
}
|
||||
@@ -271,9 +280,9 @@ internal static class DevelopmentLiveLaunchBootstrap
|
||||
}
|
||||
catch (Exception exception) when (IsExpectedEnvironmentFailure(exception))
|
||||
{
|
||||
for (var index = names.Length - 1; index >= 0; index--)
|
||||
for (var index = ArmedEnvironmentNames.Length - 1; index >= 0; index--)
|
||||
{
|
||||
var name = names[index];
|
||||
var name = ArmedEnvironmentNames[index];
|
||||
if (!originals.TryGetValue(name, out var original))
|
||||
{
|
||||
continue;
|
||||
@@ -296,6 +305,30 @@ internal static class DevelopmentLiveLaunchBootstrap
|
||||
}
|
||||
}
|
||||
|
||||
private static DevelopmentLiveBootstrapResult RejectedAndDisarm(
|
||||
IDevelopmentLiveBootstrapPlatform platform,
|
||||
string code)
|
||||
{
|
||||
// An exact Debug request that failed validation must not fall through to
|
||||
// inherited process state from an earlier shell or Visual Studio session.
|
||||
// The app also forces this requested-but-not-applied state to DryRun.
|
||||
for (var index = ArmedEnvironmentNames.Length - 1; index >= 0; index--)
|
||||
{
|
||||
var name = ArmedEnvironmentNames[index];
|
||||
try
|
||||
{
|
||||
platform.SetProcessEnvironmentVariable(name, null);
|
||||
}
|
||||
catch (Exception exception) when (IsExpectedEnvironmentFailure(exception))
|
||||
{
|
||||
// MainWindow's request provenance remains the final fail-closed
|
||||
// boundary even if the host denies cleanup of a stale value.
|
||||
}
|
||||
}
|
||||
|
||||
return Rejected(code);
|
||||
}
|
||||
|
||||
private static string? StrictString(JsonElement value) =>
|
||||
value.ValueKind == JsonValueKind.String ? value.GetString() : null;
|
||||
|
||||
|
||||
@@ -5,6 +5,19 @@ namespace MBN_STOCK_WEBVIEW.Playout.Configuration;
|
||||
|
||||
public sealed class PlayoutOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Process-local provenance set only by the exact Debug Development Live
|
||||
/// bootstrap loader. It is not a JSON or environment configuration surface.
|
||||
/// </summary>
|
||||
internal bool DevelopmentLiveBootstrapApplied { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Canonical executable-relative Cuts root captured by the Development Live
|
||||
/// loader. Final validation uses it to prevent later replacement of the
|
||||
/// verified build payload.
|
||||
/// </summary>
|
||||
internal string? DevelopmentLiveExecutableSceneDirectory { get; set; }
|
||||
|
||||
public PlayoutMode Mode { get; set; } = PlayoutMode.DryRun;
|
||||
|
||||
public string Host { get; set; } = "127.0.0.1";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Security;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using MBN_STOCK_WEBVIEW.Core.Playout;
|
||||
@@ -7,12 +8,55 @@ namespace MBN_STOCK_WEBVIEW.Playout.Configuration;
|
||||
|
||||
public static class PlayoutOptionsLoader
|
||||
{
|
||||
internal const int MaximumDevelopmentLiveConfigurationFileBytes = 64 * 1024;
|
||||
|
||||
public const string LiveAuthorizationEnvironmentVariable =
|
||||
"MBN_STOCK_PLAYOUT_AUTHORIZE_LIVE_OUTPUT";
|
||||
|
||||
public const string RequiredLiveAuthorizationValue =
|
||||
"I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH";
|
||||
|
||||
private static readonly string[] DevelopmentLiveSceneAllowlist =
|
||||
[
|
||||
"5001", "5006", "5011", "5016", "50160", "5023", "5024", "5025",
|
||||
"5026", "5029", "5032", "5037", "5068", "5070", "5072", "5074",
|
||||
"5076", "5077", "5078", "5079", "5080", "5081", "5082", "5083",
|
||||
"5084", "5085", "5086", "50860", "5087", "5088", "6001", "6067",
|
||||
"8001", "8002", "8003", "8018", "8032", "8035", "8040", "8046",
|
||||
"8051", "8056", "8061", "8067", "N5001"
|
||||
];
|
||||
|
||||
private static readonly IReadOnlySet<string> DevelopmentLiveProperties =
|
||||
new HashSet<string>(StringComparer.Ordinal)
|
||||
{
|
||||
"mode",
|
||||
"host",
|
||||
"port",
|
||||
"tcpMode",
|
||||
"clientPort",
|
||||
"sceneDirectory",
|
||||
"outputChannel",
|
||||
"layoutIndex",
|
||||
"legacySceneFadeDuration",
|
||||
"legacySceneBackgroundKind",
|
||||
"legacySceneBackgroundAssetPath",
|
||||
"legacySceneBackgroundVideoLoopCount",
|
||||
"legacySceneBackgroundVideoLoopInfinite",
|
||||
"legacyBackgroundDirectory",
|
||||
"testProcessWindowTitlePattern",
|
||||
"testSceneAllowlist",
|
||||
"trustedLiveOutputEnabled",
|
||||
"queueCapacity",
|
||||
"connectTimeoutMilliseconds",
|
||||
"operationTimeoutMilliseconds",
|
||||
"disconnectTimeoutMilliseconds",
|
||||
"processPollIntervalMilliseconds",
|
||||
"reconnectDelayMilliseconds",
|
||||
"maximumReconnectAttempts",
|
||||
"reconnectEnabled",
|
||||
"maximumAutomaticRefreshesPerTakeIn"
|
||||
};
|
||||
|
||||
public static string DefaultPath => Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"MBN_STOCK_WEBVIEW",
|
||||
@@ -43,6 +87,32 @@ public static class PlayoutOptionsLoader
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the inert LocalAppData base profile for an already-applied exact
|
||||
/// Debug Development Live bootstrap. This path deliberately ignores every
|
||||
/// process environment override and native operator folder selection. Live
|
||||
/// mode is armed from bootstrap provenance, and the scene root is pinned to
|
||||
/// the verified Cuts payload beside the executable.
|
||||
/// </summary>
|
||||
internal static PlayoutOptions LoadDevelopmentLive(
|
||||
string? path = null,
|
||||
string? baseDirectory = null)
|
||||
{
|
||||
var options = LoadDevelopmentLiveJson(
|
||||
path ?? DefaultPath,
|
||||
requireProtectedDefaultPath: path is null);
|
||||
EnsureDevelopmentLiveBaseContract(options);
|
||||
|
||||
options.Mode = PlayoutMode.Live;
|
||||
options.SceneDirectory = null;
|
||||
ApplyExecutableAssetDefaults(
|
||||
options,
|
||||
baseDirectory ?? AppContext.BaseDirectory);
|
||||
options.DevelopmentLiveExecutableSceneDirectory = options.SceneDirectory;
|
||||
options.DevelopmentLiveBootstrapApplied = true;
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies only the two non-command asset roots selected by the native settings
|
||||
/// screen. Process environment values are applied afterwards and therefore retain
|
||||
@@ -111,6 +181,287 @@ public static class PlayoutOptionsLoader
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureDevelopmentLiveBaseContract(PlayoutOptions options)
|
||||
{
|
||||
if (options.Mode != PlayoutMode.DryRun ||
|
||||
!IsLiteralLoopback(options.Host) ||
|
||||
options.Port is < 1 or > 65_535 ||
|
||||
options.TcpMode != 1 ||
|
||||
options.ClientPort != 0 ||
|
||||
options.OutputChannel is < 0 ||
|
||||
options.LayoutIndex != 10 ||
|
||||
!string.IsNullOrWhiteSpace(options.SceneDirectory) ||
|
||||
options.LegacySceneFadeDuration != 6 ||
|
||||
options.LegacySceneBackgroundKind != LegacySceneBackgroundKind.None ||
|
||||
!string.IsNullOrWhiteSpace(options.LegacySceneBackgroundAssetPath) ||
|
||||
options.LegacySceneBackgroundVideoLoopCount != 2004 ||
|
||||
!options.LegacySceneBackgroundVideoLoopInfinite ||
|
||||
!string.IsNullOrWhiteSpace(options.LegacyBackgroundDirectory) ||
|
||||
!string.IsNullOrWhiteSpace(options.TestProcessWindowTitlePattern) ||
|
||||
options.TestSceneAllowlist is null ||
|
||||
!options.TestSceneAllowlist.SequenceEqual(
|
||||
DevelopmentLiveSceneAllowlist,
|
||||
StringComparer.Ordinal) ||
|
||||
!options.TrustedLiveOutputEnabled ||
|
||||
options.QueueCapacity != 64 ||
|
||||
options.ConnectTimeoutMilliseconds != 5_000 ||
|
||||
options.OperationTimeoutMilliseconds != 5_000 ||
|
||||
options.DisconnectTimeoutMilliseconds != 3_000 ||
|
||||
options.ProcessPollIntervalMilliseconds != 1_000 ||
|
||||
options.ReconnectDelayMilliseconds != 1_000 ||
|
||||
options.ReconnectEnabled ||
|
||||
options.MaximumReconnectAttempts != 0 ||
|
||||
options.MaximumAutomaticRefreshesPerTakeIn != 0)
|
||||
{
|
||||
throw new PlayoutConfigurationException(
|
||||
"Development Live requires the exact protected local base configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
private static PlayoutOptions LoadDevelopmentLiveJson(
|
||||
string path,
|
||||
bool requireProtectedDefaultPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
if (requireProtectedDefaultPath)
|
||||
{
|
||||
EnsureProtectedDevelopmentLivePath(fullPath);
|
||||
}
|
||||
|
||||
var attributes = File.GetAttributes(fullPath);
|
||||
if ((attributes & (FileAttributes.Directory | FileAttributes.ReparsePoint)) != 0)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
byte[] json;
|
||||
using (var stream = new FileStream(
|
||||
fullPath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.Read,
|
||||
bufferSize: 4096,
|
||||
FileOptions.SequentialScan))
|
||||
{
|
||||
if (stream.Length is <= 0 or >
|
||||
MaximumDevelopmentLiveConfigurationFileBytes)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
json = new byte[checked((int)stream.Length)];
|
||||
stream.ReadExactly(json);
|
||||
}
|
||||
|
||||
using var document = JsonDocument.Parse(
|
||||
json,
|
||||
new JsonDocumentOptions
|
||||
{
|
||||
AllowTrailingCommas = false,
|
||||
CommentHandling = JsonCommentHandling.Disallow,
|
||||
MaxDepth = 4
|
||||
});
|
||||
var root = document.RootElement;
|
||||
if (root.ValueKind != JsonValueKind.Object)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var property in root.EnumerateObject())
|
||||
{
|
||||
if (!DevelopmentLiveProperties.Contains(property.Name) ||
|
||||
!seen.Add(property.Name))
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
}
|
||||
|
||||
if (seen.Count != DevelopmentLiveProperties.Count ||
|
||||
!IsExactString(root, "mode", "DryRun") ||
|
||||
!IsLoopbackString(root, "host") ||
|
||||
!IsIntegerInRange(root, "port", 1, 65_535) ||
|
||||
!IsExactInteger(root, "tcpMode", 1) ||
|
||||
!IsExactInteger(root, "clientPort", 0) ||
|
||||
!IsNull(root, "sceneDirectory") ||
|
||||
!IsNullableNonNegativeInteger(root, "outputChannel") ||
|
||||
!IsExactInteger(root, "layoutIndex", 10) ||
|
||||
!IsExactInteger(root, "legacySceneFadeDuration", 6) ||
|
||||
!IsExactString(root, "legacySceneBackgroundKind", "None") ||
|
||||
!IsNull(root, "legacySceneBackgroundAssetPath") ||
|
||||
!IsExactInteger(root, "legacySceneBackgroundVideoLoopCount", 2004) ||
|
||||
!IsExactBoolean(root, "legacySceneBackgroundVideoLoopInfinite", true) ||
|
||||
!IsNull(root, "legacyBackgroundDirectory") ||
|
||||
!IsNull(root, "testProcessWindowTitlePattern") ||
|
||||
!IsExactStringArray(
|
||||
root,
|
||||
"testSceneAllowlist",
|
||||
DevelopmentLiveSceneAllowlist) ||
|
||||
!IsExactBoolean(root, "trustedLiveOutputEnabled", true) ||
|
||||
!IsExactInteger(root, "queueCapacity", 64) ||
|
||||
!IsExactInteger(root, "connectTimeoutMilliseconds", 5_000) ||
|
||||
!IsExactInteger(root, "operationTimeoutMilliseconds", 5_000) ||
|
||||
!IsExactInteger(root, "disconnectTimeoutMilliseconds", 3_000) ||
|
||||
!IsExactInteger(root, "processPollIntervalMilliseconds", 1_000) ||
|
||||
!IsExactInteger(root, "reconnectDelayMilliseconds", 1_000) ||
|
||||
!IsExactInteger(root, "maximumReconnectAttempts", 0) ||
|
||||
!IsExactBoolean(root, "reconnectEnabled", false) ||
|
||||
!IsExactInteger(root, "maximumAutomaticRefreshesPerTakeIn", 0))
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<PlayoutOptions>(
|
||||
json,
|
||||
JsonOptions())
|
||||
?? throw new InvalidDataException();
|
||||
}
|
||||
catch (Exception exception) when (
|
||||
exception is IOException or UnauthorizedAccessException or SecurityException or
|
||||
InvalidDataException or ArgumentException or NotSupportedException or
|
||||
JsonException)
|
||||
{
|
||||
throw new PlayoutConfigurationException(
|
||||
"Development Live local configuration is invalid.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureProtectedDevelopmentLivePath(string fullPath)
|
||||
{
|
||||
var localApplicationData = Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.LocalApplicationData);
|
||||
if (string.IsNullOrWhiteSpace(localApplicationData))
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
var localRoot = Path.TrimEndingDirectorySeparator(
|
||||
Path.GetFullPath(localApplicationData));
|
||||
var expectedPath = Path.Combine(
|
||||
localRoot,
|
||||
"MBN_STOCK_WEBVIEW",
|
||||
"Config",
|
||||
"playout.local.json");
|
||||
if (!string.Equals(fullPath, expectedPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
while (!string.IsNullOrWhiteSpace(directory) &&
|
||||
!string.Equals(directory, localRoot, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if ((File.GetAttributes(directory) & FileAttributes.ReparsePoint) != 0)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
directory = Path.GetDirectoryName(directory);
|
||||
}
|
||||
|
||||
if (!string.Equals(directory, localRoot, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsExactString(
|
||||
JsonElement root,
|
||||
string propertyName,
|
||||
string expected)
|
||||
{
|
||||
var value = root.GetProperty(propertyName);
|
||||
return value.ValueKind == JsonValueKind.String &&
|
||||
string.Equals(value.GetString(), expected, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static bool IsLoopbackString(JsonElement root, string propertyName)
|
||||
{
|
||||
var value = root.GetProperty(propertyName);
|
||||
return value.ValueKind == JsonValueKind.String &&
|
||||
IsLiteralLoopback(value.GetString());
|
||||
}
|
||||
|
||||
private static bool IsLiteralLoopback(string? host) =>
|
||||
string.Equals(host, "127.0.0.1", StringComparison.Ordinal) ||
|
||||
string.Equals(host, "::1", StringComparison.Ordinal);
|
||||
|
||||
private static bool IsIntegerInRange(
|
||||
JsonElement root,
|
||||
string propertyName,
|
||||
int minimum,
|
||||
int maximum)
|
||||
{
|
||||
var value = root.GetProperty(propertyName);
|
||||
return value.ValueKind == JsonValueKind.Number &&
|
||||
value.TryGetInt32(out var parsed) &&
|
||||
parsed >= minimum &&
|
||||
parsed <= maximum;
|
||||
}
|
||||
|
||||
private static bool IsExactInteger(
|
||||
JsonElement root,
|
||||
string propertyName,
|
||||
int expected) =>
|
||||
IsIntegerInRange(root, propertyName, expected, expected);
|
||||
|
||||
private static bool IsNullableNonNegativeInteger(
|
||||
JsonElement root,
|
||||
string propertyName)
|
||||
{
|
||||
var value = root.GetProperty(propertyName);
|
||||
return value.ValueKind == JsonValueKind.Null ||
|
||||
value.ValueKind == JsonValueKind.Number &&
|
||||
value.TryGetInt32(out var parsed) &&
|
||||
parsed >= 0;
|
||||
}
|
||||
|
||||
private static bool IsExactBoolean(
|
||||
JsonElement root,
|
||||
string propertyName,
|
||||
bool expected)
|
||||
{
|
||||
var value = root.GetProperty(propertyName);
|
||||
return expected
|
||||
? value.ValueKind == JsonValueKind.True
|
||||
: value.ValueKind == JsonValueKind.False;
|
||||
}
|
||||
|
||||
private static bool IsNull(JsonElement root, string propertyName) =>
|
||||
root.GetProperty(propertyName).ValueKind == JsonValueKind.Null;
|
||||
|
||||
private static bool IsExactStringArray(
|
||||
JsonElement root,
|
||||
string propertyName,
|
||||
IReadOnlyList<string> expected)
|
||||
{
|
||||
var value = root.GetProperty(propertyName);
|
||||
if (value.ValueKind != JsonValueKind.Array ||
|
||||
value.GetArrayLength() != expected.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
foreach (var item in value.EnumerateArray())
|
||||
{
|
||||
if (item.ValueKind != JsonValueKind.String ||
|
||||
!string.Equals(
|
||||
item.GetString(),
|
||||
expected[index],
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static PlayoutOptions LoadJson(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net;
|
||||
using MBN_STOCK_WEBVIEW.Core.Playout;
|
||||
using MBN_STOCK_WEBVIEW.Core.Playout.Scenes;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Interop;
|
||||
using MBN_STOCK_WEBVIEW.Playout.Runtime;
|
||||
|
||||
@@ -125,6 +126,52 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
throw Invalid(nameof(options.TestSceneAllowlist));
|
||||
}
|
||||
|
||||
if (options.DevelopmentLiveBootstrapApplied)
|
||||
{
|
||||
if (options.Mode != PlayoutMode.Live)
|
||||
{
|
||||
throw Invalid(nameof(options.Mode));
|
||||
}
|
||||
|
||||
if (!IsLiteralLoopback(host))
|
||||
{
|
||||
throw Invalid(nameof(options.Host));
|
||||
}
|
||||
|
||||
string? expectedSceneDirectory;
|
||||
try
|
||||
{
|
||||
expectedSceneDirectory = string.IsNullOrWhiteSpace(
|
||||
options.DevelopmentLiveExecutableSceneDirectory)
|
||||
? null
|
||||
: Path.TrimEndingDirectorySeparator(Path.GetFullPath(
|
||||
options.DevelopmentLiveExecutableSceneDirectory));
|
||||
}
|
||||
catch (Exception exception) when (
|
||||
exception is ArgumentException or IOException or NotSupportedException or
|
||||
System.Security.SecurityException)
|
||||
{
|
||||
throw Invalid(nameof(options.SceneDirectory));
|
||||
}
|
||||
|
||||
if (expectedSceneDirectory is null ||
|
||||
!string.Equals(
|
||||
sceneDirectory,
|
||||
expectedSceneDirectory,
|
||||
StringComparison.OrdinalIgnoreCase) ||
|
||||
options.LegacySceneFadeDuration != 6 ||
|
||||
options.LegacySceneBackgroundKind != LegacySceneBackgroundKind.None ||
|
||||
!string.IsNullOrWhiteSpace(options.LegacySceneBackgroundAssetPath) ||
|
||||
!string.IsNullOrWhiteSpace(options.LegacyBackgroundDirectory) ||
|
||||
options.ReconnectEnabled ||
|
||||
options.MaximumReconnectAttempts != 0 ||
|
||||
options.MaximumAutomaticRefreshesPerTakeIn != 0)
|
||||
{
|
||||
throw new PlayoutConfigurationException(
|
||||
"Development Live configuration no longer matches its protected startup contract.");
|
||||
}
|
||||
}
|
||||
|
||||
if (options.Mode == PlayoutMode.Test)
|
||||
{
|
||||
if (!IsLiteralLoopback(host))
|
||||
|
||||
Reference in New Issue
Block a user