fix: initialize selected runtime for live playout
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
<TextBlock
|
||||
FontSize="15"
|
||||
Foreground="#FFBFD2E6"
|
||||
Text="기존 코더의 Cuts 폴더와 Res 폴더를 선택하면 해당 경로만 저장합니다."
|
||||
Text="기존 코더의 Cuts 폴더와 Res 폴더를 선택하면 이후 실행에서도 그 경로를 그대로 사용합니다."
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
Text="2. 설정/INI 폴더 (Res)" />
|
||||
<TextBlock
|
||||
Foreground="#FF8FAAC5"
|
||||
Text="폴더 안의 파일 전체를 검사하거나 복사하지 않고 선택한 경로를 그대로 사용합니다."
|
||||
Text="선택한 폴더의 MmoneyCoder.ini를 DB 설정으로 검증하고 계속 직접 사용합니다. 자격증명 파일은 복사하지 않습니다."
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
x:Name="ResourcePathText"
|
||||
@@ -115,7 +115,7 @@
|
||||
<TextBlock
|
||||
Foreground="#FF8FAAC5"
|
||||
FontSize="13"
|
||||
Text="설정 시작은 경로만 저장합니다. 빌드, DB, K3D 또는 Tornado2 작업은 실행하지 않으며 완료 후 Visual Studio에서 F5를 한 번 더 누르면 앱을 빌드해 시작합니다."
|
||||
Text="설정 시작은 선택 경로, C:\K3DAsyncEngine의 x64 등록/파일, 보호된 Live 설정과 Debug x64 빌드를 검증합니다. 이 과정에서는 DB에 연결하거나 Tornado2/PGM 송출 명령을 보내지 않습니다."
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using MBN_STOCK_WEBVIEW.Infrastructure;
|
||||
using MBN_STOCK_WEBVIEW.LegacyApplication;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Windows.Graphics;
|
||||
@@ -12,8 +12,8 @@ namespace MBN_STOCK_WEBVIEW.LegacyParityApp;
|
||||
public sealed partial class FirstRunSetupWindow : Window
|
||||
{
|
||||
private const string RepositoryRootMetadataKey = "SourceRepositoryRoot";
|
||||
private const string LocalBuildPropsFileName =
|
||||
"Directory.Build.local.props";
|
||||
private const string SetupScriptRelativePath =
|
||||
@"scripts\Initialize-ExistingDevelopmentPc.ps1";
|
||||
|
||||
private string? _cutsDirectory;
|
||||
private string? _resourceDirectory;
|
||||
@@ -82,7 +82,7 @@ public sealed partial class FirstRunSetupWindow : Window
|
||||
RefreshReadyState();
|
||||
}
|
||||
|
||||
private void OnInstallClicked(object sender, RoutedEventArgs e)
|
||||
private async void OnInstallClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_setupRunning)
|
||||
{
|
||||
@@ -112,14 +112,14 @@ public sealed partial class FirstRunSetupWindow : Window
|
||||
}
|
||||
|
||||
SetSetupRunning(true);
|
||||
SetupStatusBar.Title = "경로 저장 중";
|
||||
SetupStatusBar.Title = "개발 PC 설정 중";
|
||||
SetupStatusBar.Message =
|
||||
"선택한 Cuts/Res 폴더 경로를 저장하고 있습니다.";
|
||||
"DB 설정, 표준 K3D 설치, Live 승인과 Debug x64 빌드를 검증하고 있습니다.";
|
||||
SetupStatusBar.Severity = InfoBarSeverity.Informational;
|
||||
|
||||
try
|
||||
{
|
||||
SaveSelectedRuntimePaths(
|
||||
await RunVerifiedSetupAsync(
|
||||
repositoryRoot,
|
||||
runtimeRoot!,
|
||||
_cutsDirectory!,
|
||||
@@ -127,7 +127,7 @@ public sealed partial class FirstRunSetupWindow : Window
|
||||
|
||||
SetupStatusBar.Title = "설정 완료";
|
||||
SetupStatusBar.Message =
|
||||
"폴더 경로를 저장했습니다. 이 창을 닫고 Visual Studio에서 F5를 한 번 더 누르세요.";
|
||||
"Cuts/Res, DB, K3D 및 Live 설정을 검증했습니다. 이 창을 닫고 Visual Studio에서 F5를 한 번 더 누르세요.";
|
||||
SetupStatusBar.Severity = InfoBarSeverity.Success;
|
||||
InstallButton.Visibility = Visibility.Collapsed;
|
||||
CloseButton.Visibility = Visibility.Visible;
|
||||
@@ -135,7 +135,7 @@ public sealed partial class FirstRunSetupWindow : Window
|
||||
catch
|
||||
{
|
||||
ShowError(
|
||||
"폴더 경로를 저장하지 못했습니다. 폴더와 Git 소스 위치를 확인해 주세요.");
|
||||
"개발 PC 자동 설정에 실패했습니다. Res\\MmoneyCoder.ini, C:\\K3DAsyncEngine x64 등록, Tornado2의 127.0.0.1:30001 설정을 확인해 주세요.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -211,7 +211,7 @@ public sealed partial class FirstRunSetupWindow : Window
|
||||
InstallButton.IsEnabled = true;
|
||||
SetupStatusBar.Title = "준비 완료";
|
||||
SetupStatusBar.Message =
|
||||
"설정 시작을 누르면 선택한 Cuts/Res 경로를 이 PC에 적용합니다.";
|
||||
"설정 시작을 누르면 Cuts/Res, DB, 표준 K3D 및 Development Live를 검증합니다.";
|
||||
SetupStatusBar.Severity = InfoBarSeverity.Success;
|
||||
}
|
||||
|
||||
@@ -267,6 +267,18 @@ public sealed partial class FirstRunSetupWindow : Window
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_ = new LegacyIniDatabaseOptionsLoader(_ => null).Load(
|
||||
Path.Combine(resource, "MmoneyCoder.ini"));
|
||||
}
|
||||
catch (DatabaseConfigurationException)
|
||||
{
|
||||
warningMessage =
|
||||
"선택한 Res 폴더의 MmoneyCoder.ini에 유효한 Oracle/MariaDB 설정이 모두 필요합니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
runtimeRoot = cutsParent;
|
||||
return true;
|
||||
}
|
||||
@@ -301,119 +313,141 @@ public sealed partial class FirstRunSetupWindow : Window
|
||||
return repositoryRoot;
|
||||
}
|
||||
|
||||
private static void SaveSelectedRuntimePaths(
|
||||
private static async Task RunVerifiedSetupAsync(
|
||||
string repositoryRoot,
|
||||
string runtimeRoot,
|
||||
string cutsDirectory,
|
||||
string resourceDirectory)
|
||||
{
|
||||
var store = new LegacyOperatorSettingsStore();
|
||||
var loaded = store.Load();
|
||||
var current = loaded.Failure == LegacyOperatorSettingsStoreFailure.None
|
||||
? loaded.Settings
|
||||
: LegacyOperatorSettings.Default;
|
||||
var saved = store.Save(current with
|
||||
var powershellPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.System),
|
||||
"WindowsPowerShell",
|
||||
"v1.0",
|
||||
"powershell.exe");
|
||||
var setupScriptPath = Path.Combine(
|
||||
repositoryRoot,
|
||||
SetupScriptRelativePath);
|
||||
EnsureOrdinaryFile(
|
||||
powershellPath,
|
||||
"Windows PowerShell을 찾을 수 없습니다.");
|
||||
EnsureOrdinaryFile(
|
||||
setupScriptPath,
|
||||
"검증된 개발 PC 초기화 스크립트를 찾을 수 없습니다.");
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
SceneDirectory = cutsDirectory,
|
||||
ResourceDirectory = resourceDirectory
|
||||
});
|
||||
if (!saved.Succeeded)
|
||||
FileName = powershellPath,
|
||||
WorkingDirectory = repositoryRoot,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
AddArgument("-NoLogo");
|
||||
AddArgument("-NoProfile");
|
||||
AddArgument("-NonInteractive");
|
||||
AddArgument("-ExecutionPolicy");
|
||||
AddArgument("Bypass");
|
||||
AddArgument("-File");
|
||||
AddArgument(setupScriptPath);
|
||||
AddArgument("-LegacyRuntimeSourceRoot");
|
||||
AddArgument(runtimeRoot);
|
||||
AddArgument("-NoFolderPicker");
|
||||
AddArgument("-SkipDatabaseProfile");
|
||||
AddArgument("-ConfigureDevelopmentLive");
|
||||
AddArgument("-PlayoutHost");
|
||||
AddArgument("127.0.0.1");
|
||||
AddArgument("-PlayoutPort");
|
||||
AddArgument("30001");
|
||||
AddArgument("-PinRegisteredK3D");
|
||||
|
||||
using var process = new Process
|
||||
{
|
||||
StartInfo = startInfo
|
||||
};
|
||||
if (!process.Start())
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The selected runtime folders could not be saved.");
|
||||
"The verified development-PC initializer could not be started.");
|
||||
}
|
||||
|
||||
SaveRuntimeBinding(repositoryRoot, runtimeRoot);
|
||||
var standardOutput = DrainOutputAsync(process.StandardOutput);
|
||||
var standardError = DrainOutputAsync(process.StandardError);
|
||||
await process.WaitForExitAsync();
|
||||
await Task.WhenAll(standardOutput, standardError);
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"The verified development-PC initializer failed with exit code {process.ExitCode}.");
|
||||
}
|
||||
|
||||
var loaded = new LegacyOperatorSettingsStore().Load();
|
||||
if (loaded.Failure != LegacyOperatorSettingsStoreFailure.None ||
|
||||
!string.Equals(
|
||||
loaded.Settings.SceneDirectory,
|
||||
cutsDirectory,
|
||||
StringComparison.OrdinalIgnoreCase) ||
|
||||
!string.Equals(
|
||||
loaded.Settings.ResourceDirectory,
|
||||
resourceDirectory,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The verified initializer did not retain the selected runtime folders.");
|
||||
}
|
||||
|
||||
EnsureOrdinaryFile(
|
||||
Path.Combine(repositoryRoot, "Directory.Build.local.props"),
|
||||
"The verified initializer did not retain the local build binding.");
|
||||
var localApplicationData = Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.LocalApplicationData);
|
||||
var configurationRoot = Path.Combine(
|
||||
localApplicationData,
|
||||
"MBN_STOCK_WEBVIEW",
|
||||
"Config");
|
||||
foreach (var fileName in new[]
|
||||
{
|
||||
"k3d-pins.local.json",
|
||||
"playout.local.json",
|
||||
"playout.development-live.local.json"
|
||||
})
|
||||
{
|
||||
EnsureOrdinaryFile(
|
||||
Path.Combine(configurationRoot, fileName),
|
||||
"The verified initializer did not retain every protected Live setting.");
|
||||
}
|
||||
|
||||
void AddArgument(string value) => startInfo.ArgumentList.Add(value);
|
||||
}
|
||||
|
||||
private static void SaveRuntimeBinding(
|
||||
string repositoryRoot,
|
||||
string runtimeRoot)
|
||||
private static async Task DrainOutputAsync(StreamReader reader)
|
||||
{
|
||||
var destination = Path.Combine(
|
||||
repositoryRoot,
|
||||
LocalBuildPropsFileName);
|
||||
if (Directory.Exists(destination))
|
||||
var buffer = new char[4096];
|
||||
while (await reader.ReadAsync(buffer.AsMemory()) > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The local runtime binding path is not a file.");
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(destination))
|
||||
{
|
||||
var attributes = File.GetAttributes(destination);
|
||||
if ((attributes & (FileAttributes.Directory |
|
||||
FileAttributes.ReparsePoint |
|
||||
FileAttributes.Device)) != 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The local runtime binding file is unsafe.");
|
||||
}
|
||||
}
|
||||
|
||||
var bytes = CreateRuntimeBinding(runtimeRoot);
|
||||
var temporary = Path.Combine(
|
||||
repositoryRoot,
|
||||
$".{LocalBuildPropsFileName}.{Guid.NewGuid():N}.tmp");
|
||||
private static void EnsureOrdinaryFile(string path, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = new FileStream(
|
||||
temporary,
|
||||
FileMode.CreateNew,
|
||||
FileAccess.Write,
|
||||
FileShare.None,
|
||||
bufferSize: 4096,
|
||||
FileOptions.WriteThrough))
|
||||
var attributes = File.GetAttributes(Path.GetFullPath(path));
|
||||
if ((attributes & (FileAttributes.Directory |
|
||||
FileAttributes.ReparsePoint |
|
||||
FileAttributes.Device)) == 0)
|
||||
{
|
||||
stream.Write(bytes);
|
||||
stream.Flush(flushToDisk: true);
|
||||
}
|
||||
|
||||
File.Move(temporary, destination, overwrite: true);
|
||||
temporary = string.Empty;
|
||||
if (!File.ReadAllBytes(destination).AsSpan().SequenceEqual(bytes))
|
||||
{
|
||||
throw new IOException(
|
||||
"The local runtime binding failed verification.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
finally
|
||||
catch (Exception exception) when (
|
||||
exception is ArgumentException or IOException or
|
||||
UnauthorizedAccessException or NotSupportedException or
|
||||
System.Security.SecurityException)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(temporary) &&
|
||||
File.Exists(temporary))
|
||||
{
|
||||
File.Delete(temporary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] CreateRuntimeBinding(string runtimeRoot)
|
||||
{
|
||||
using var stream = new MemoryStream(capacity: 512);
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
|
||||
Indent = true,
|
||||
NewLineChars = Environment.NewLine,
|
||||
NewLineHandling = NewLineHandling.Replace
|
||||
};
|
||||
using (var writer = XmlWriter.Create(stream, settings))
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteStartElement("Project");
|
||||
writer.WriteStartElement("PropertyGroup");
|
||||
writer.WriteElementString(
|
||||
"LegacyRuntimeSourceRoot",
|
||||
runtimeRoot);
|
||||
writer.WriteElementString(
|
||||
"LegacyRuntimeAssetsMode",
|
||||
"Required");
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
private void SetSetupRunning(bool running)
|
||||
|
||||
@@ -185,8 +185,8 @@
|
||||
</Content>
|
||||
<!--
|
||||
The active legacy DB INI contains credentials and is never a build input or
|
||||
MSIX Content. Local F5 and packaged development use the current user's
|
||||
LocalAppData overlay. Historical copies, backups, archives and
|
||||
MSIX Content. Development Live reads the exact selected external
|
||||
Res\MmoneyCoder.ini at runtime. Historical copies, backups, archives and
|
||||
afiedt.buf.txt are not included at all.
|
||||
-->
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
@@ -202,7 +202,7 @@
|
||||
Text="Legacy runtime assets were not found. Building the safe first-run setup app. Start Debug x64 and select the existing Cuts and Res folders." />
|
||||
<Message Condition="'$(LegacyRuntimeAssetsEnabled)' == 'true'"
|
||||
Importance="high"
|
||||
Text="External legacy runtime paths are enabled. Cuts files remain in the selected folder and database credentials remain in the current user's LocalAppData overlay." />
|
||||
Text="External legacy runtime paths are enabled. Cuts and the credential-bearing Res\MmoneyCoder.ini remain in the selected external folders." />
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyRuntimeSourceRoot)')"
|
||||
Text="Legacy runtime source root was not found: $(LegacyRuntimeSourceRoot). Run scripts\Initialize-ExistingDevelopmentPc.ps1 to create the verified local Required binding, or use /p:LegacyRuntimeAssetsMode=Auto for a source-only setup build." />
|
||||
@@ -242,8 +242,8 @@
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Database credentials are never build inputs. Full Debug, publish and MSIX
|
||||
builds use the same user-only LocalAppData overlay as a clean handoff PC.
|
||||
Database credentials are never build inputs. Development Live resolves the
|
||||
selected external Res\MmoneyCoder.ini only at runtime.
|
||||
-->
|
||||
<Target Name="ScrubLegacyDatabaseIniFromBuildOutput"
|
||||
BeforeTargets="PrepareForBuild">
|
||||
|
||||
@@ -139,16 +139,17 @@ public sealed partial class MainWindow : Window
|
||||
|
||||
// A Gate A plan pins database.local.json by path, size and SHA-256.
|
||||
// Never inspect either packaged or operator-selected legacy INI files
|
||||
// during that one-shot validation process.
|
||||
// during that one-shot validation process. Ordinary Development Live
|
||||
// uses the selected Res\MmoneyCoder.ini as its exact DB authority.
|
||||
var selectedLegacyDatabaseIni =
|
||||
_isDevelopmentLiveLaunch ||
|
||||
selectedResourceDirectory is null
|
||||
? null
|
||||
: Path.Combine(selectedResourceDirectory, "MmoneyCoder.ini");
|
||||
_databaseRuntime = _playoutLaunchAuthorization.IsGateA
|
||||
? DatabaseRuntime.CreateDefault()
|
||||
: _isDevelopmentLiveLaunch
|
||||
? DatabaseRuntime.CreateDevelopmentLiveLocalOverride()
|
||||
? DatabaseRuntime.CreateDevelopmentLiveLocalOverride(
|
||||
selectedResourceDirectory)
|
||||
: DatabaseRuntime.CreateLegacyExecutableDefault(
|
||||
AppContext.BaseDirectory,
|
||||
legacyOverridePath: selectedLegacyDatabaseIni,
|
||||
@@ -208,11 +209,21 @@ public sealed partial class MainWindow : Window
|
||||
operatorCatalogSchemaValidationService =
|
||||
new LegacyOperatorCatalogSchemaValidationService(_databaseRuntime.Executor);
|
||||
}
|
||||
catch
|
||||
catch (Exception exception)
|
||||
{
|
||||
var developmentLiveDatabaseConfigurationFailed =
|
||||
_isDevelopmentLiveLaunch &&
|
||||
exception is DatabaseConfigurationException;
|
||||
if (developmentLiveDatabaseConfigurationFailed)
|
||||
{
|
||||
_developmentLiveStartupBlocked = true;
|
||||
}
|
||||
|
||||
_databaseRuntime = null;
|
||||
fixedPagePlanProvider = null;
|
||||
initializationError = _developmentLiveStartupBlocked
|
||||
initializationError = developmentLiveDatabaseConfigurationFailed
|
||||
? "Development Live 데이터베이스 설정을 확인할 수 없습니다."
|
||||
: _developmentLiveStartupBlocked
|
||||
? App.DevelopmentLiveStartupFailureMessage ??
|
||||
"Live playout startup validation failed."
|
||||
: IsSourceOnlyBuild
|
||||
|
||||
Reference in New Issue
Block a user