feat: initialize existing development playout PCs

This commit is contained in:
2026-07-26 23:11:29 +09:00
parent 8b628908f0
commit 483b2785a0
18 changed files with 4029 additions and 74 deletions

View File

@@ -62,6 +62,73 @@ public sealed class DatabaseRuntime
return CreateDefault(fallbackConfigurationPath);
}
/// <summary>
/// Creates the Development Live database runtime from the one protected
/// current-user LocalAppData legacy INI, or from the protected LocalAppData
/// JSON when that INI is absent. Unlike the ordinary compatibility path, this
/// never probes executable or operator-selected Res folders and ignores all
/// database environment overrides.
/// </summary>
public static DatabaseRuntime CreateDevelopmentLiveLocalOverride()
{
var localApplicationData = Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData);
return CreateDevelopmentLiveLocalOverride(localApplicationData);
}
internal static DatabaseRuntime CreateDevelopmentLiveLocalOverride(
string localApplicationData)
{
try
{
ArgumentException.ThrowIfNullOrWhiteSpace(localApplicationData);
var localRoot = Path.TrimEndingDirectorySeparator(
Path.GetFullPath(localApplicationData));
var resourceDirectory = Path.Combine(
localRoot,
"MBN_STOCK_WEBVIEW",
"Res");
var legacyIniPath = Path.Combine(resourceDirectory, "MmoneyCoder.ini");
if (TryEnsureOrdinaryFile(legacyIniPath))
{
EnsureNoReparsePointDirectoryChain(resourceDirectory, localRoot);
return Create(
new LegacyIniDatabaseOptionsLoader(_ => null).Load(legacyIniPath));
}
var configurationDirectory = Path.Combine(
localRoot,
"MBN_STOCK_WEBVIEW",
"Config");
EnsureNoReparsePointDirectoryChain(configurationDirectory, localRoot);
var jsonPath = Path.Combine(
configurationDirectory,
"database.local.json");
EnsureOrdinaryFile(jsonPath);
var options = new DatabaseOptionsLoader(_ => null).Load(jsonPath);
if (!options.IsConfigured(MMoneyCoderSharp.Data.DataSourceKind.Oracle) ||
!options.IsConfigured(MMoneyCoderSharp.Data.DataSourceKind.MariaDb))
{
throw new DatabaseConfigurationException(
"Development Live database configuration is unavailable.");
}
return Create(options);
}
catch (DatabaseConfigurationException)
{
throw;
}
catch (Exception exception) when (
exception is ArgumentException or IOException or UnauthorizedAccessException or
InvalidDataException or NotSupportedException or
System.Security.SecurityException)
{
throw new DatabaseConfigurationException(
"Development Live database configuration is unavailable.");
}
}
private static string? ResolveLegacyOverridePath()
{
var localAppData = Environment.GetFolderPath(
@@ -75,6 +142,56 @@ public sealed class DatabaseRuntime
"MmoneyCoder.ini");
}
private static void EnsureNoReparsePointDirectoryChain(
string directory,
string trustedRoot)
{
var current = Path.TrimEndingDirectorySeparator(Path.GetFullPath(directory));
var root = Path.TrimEndingDirectorySeparator(Path.GetFullPath(trustedRoot));
while (!string.Equals(current, root, StringComparison.OrdinalIgnoreCase))
{
if ((File.GetAttributes(current) & FileAttributes.ReparsePoint) != 0)
{
throw new InvalidDataException();
}
var parent = Path.GetDirectoryName(current);
if (string.IsNullOrWhiteSpace(parent) ||
string.Equals(parent, current, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidDataException();
}
current = Path.TrimEndingDirectorySeparator(Path.GetFullPath(parent));
}
}
private static void EnsureOrdinaryFile(string path)
{
var attributes = File.GetAttributes(Path.GetFullPath(path));
if ((attributes & (FileAttributes.Directory | FileAttributes.ReparsePoint)) != 0)
{
throw new InvalidDataException();
}
}
private static bool TryEnsureOrdinaryFile(string path)
{
try
{
EnsureOrdinaryFile(path);
return true;
}
catch (FileNotFoundException)
{
return false;
}
catch (DirectoryNotFoundException)
{
return false;
}
}
public static DatabaseRuntime Create(DatabaseOptions options)
{
ArgumentNullException.ThrowIfNull(options);