feat: add native runtime settings
This commit is contained in:
406
src/MBN_STOCK_WEBVIEW.LegacyParityApp/MainWindow.Settings.cs
Normal file
406
src/MBN_STOCK_WEBVIEW.LegacyParityApp/MainWindow.Settings.cs
Normal file
@@ -0,0 +1,406 @@
|
||||
using MBN_STOCK_WEBVIEW.Infrastructure;
|
||||
using MBN_STOCK_WEBVIEW.LegacyApplication;
|
||||
using MBN_STOCK_WEBVIEW.LegacyBridge;
|
||||
using MMoneyCoderSharp.Data;
|
||||
using Windows.Storage.Pickers;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace MBN_STOCK_WEBVIEW.LegacyParityApp;
|
||||
|
||||
public sealed partial class MainWindow
|
||||
{
|
||||
private long _operatorSettingsRevision = 1;
|
||||
private string _operatorSettingsMessage = string.Empty;
|
||||
private LegacyOperatorSettingsMessageKind _operatorSettingsMessageKind =
|
||||
LegacyOperatorSettingsMessageKind.Neutral;
|
||||
private LegacyOperatorFolderSnapshot _designFolderSnapshot = new(
|
||||
"앱 기본 디자인 폴더",
|
||||
IsCustom: false,
|
||||
IsValid: false,
|
||||
"확인 중");
|
||||
private LegacyOperatorFolderSnapshot _resourceFolderSnapshot = new(
|
||||
"앱 기본 설정 폴더",
|
||||
IsCustom: false,
|
||||
IsValid: false,
|
||||
"확인 중");
|
||||
private LegacyOperatorFolderSnapshot _backgroundFolderSnapshot = new(
|
||||
"장면 폴더 기준 자동 탐색",
|
||||
IsCustom: false,
|
||||
IsValid: true,
|
||||
"필요할 때 선택");
|
||||
|
||||
private void InitializeOperatorSettingsStatus(string? warningMessage)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(warningMessage))
|
||||
{
|
||||
_operatorSettingsMessage = warningMessage;
|
||||
_operatorSettingsMessageKind = LegacyOperatorSettingsMessageKind.Warning;
|
||||
}
|
||||
|
||||
RefreshOperatorFolderSnapshots();
|
||||
}
|
||||
|
||||
private async Task HandleOperatorSettingsIntentAsync(
|
||||
LegacyUiIntent intent,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
switch (intent)
|
||||
{
|
||||
case LegacyChooseOperatorFolderIntent choose:
|
||||
await ChooseOperatorFolderAsync(choose.Kind, cancellationToken);
|
||||
break;
|
||||
case LegacyResetOperatorFolderIntent reset:
|
||||
ResetOperatorFolder(reset.Kind);
|
||||
break;
|
||||
case LegacySetOperatorNavigationExpandedIntent navigation:
|
||||
SaveOperatorSettings(
|
||||
_operatorSettings with
|
||||
{
|
||||
NavigationExpanded = navigation.Expanded
|
||||
},
|
||||
navigation.Expanded
|
||||
? "왼쪽 메뉴를 펼쳐서 시작하도록 저장했습니다."
|
||||
: "왼쪽 메뉴를 접어서 시작하도록 저장했습니다.");
|
||||
break;
|
||||
default:
|
||||
SetOperatorSettingsMessage(
|
||||
"지원하지 않는 설정 요청입니다.",
|
||||
LegacyOperatorSettingsMessageKind.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ChooseOperatorFolderAsync(
|
||||
LegacyOperatorFolderKind kind,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
var picker = new FolderPicker
|
||||
{
|
||||
SuggestedStartLocation = PickerLocationId.ComputerFolder,
|
||||
ViewMode = PickerViewMode.List
|
||||
};
|
||||
picker.FileTypeFilter.Add("*");
|
||||
var owner = _windowHandle != 0
|
||||
? _windowHandle
|
||||
: WindowNative.GetWindowHandle(this);
|
||||
InitializeWithWindow.Initialize(picker, owner);
|
||||
var selected = await picker.PickSingleFolderAsync();
|
||||
if (selected is null)
|
||||
{
|
||||
SetOperatorSettingsMessage(
|
||||
"폴더 선택을 취소했습니다.",
|
||||
LegacyOperatorSettingsMessageKind.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var validationKind = ToSettingsFolderKind(kind);
|
||||
if (!LegacyOperatorFolderValidator.TryValidate(
|
||||
validationKind,
|
||||
selected.Path,
|
||||
out var validated,
|
||||
out var warningMessage) ||
|
||||
validated is null)
|
||||
{
|
||||
SetOperatorSettingsMessage(
|
||||
warningMessage ?? "선택한 폴더를 사용할 수 없습니다.",
|
||||
LegacyOperatorSettingsMessageKind.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind == LegacyOperatorFolderKind.Resource &&
|
||||
validated.DatabaseIniDetected &&
|
||||
!CanReadSelectedDatabaseProfile(validated.CanonicalPath))
|
||||
{
|
||||
SetOperatorSettingsMessage(
|
||||
"선택한 설정 폴더의 DB 설정 파일을 검증할 수 없습니다.",
|
||||
LegacyOperatorSettingsMessageKind.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var next = kind switch
|
||||
{
|
||||
LegacyOperatorFolderKind.Design => _operatorSettings with
|
||||
{
|
||||
SceneDirectory = validated.CanonicalPath
|
||||
},
|
||||
LegacyOperatorFolderKind.Resource => _operatorSettings with
|
||||
{
|
||||
ResourceDirectory = validated.CanonicalPath
|
||||
},
|
||||
LegacyOperatorFolderKind.Background => _operatorSettings with
|
||||
{
|
||||
BackgroundDirectory = validated.CanonicalPath
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
||||
};
|
||||
SaveOperatorSettings(
|
||||
next,
|
||||
$"{OperatorFolderLabel(kind)} 폴더를 저장했습니다. 앱을 다시 시작하면 적용됩니다.");
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
SetOperatorSettingsMessage(
|
||||
"폴더 선택 창을 열거나 선택 결과를 저장하지 못했습니다.",
|
||||
LegacyOperatorSettingsMessageKind.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetOperatorFolder(LegacyOperatorFolderKind kind)
|
||||
{
|
||||
var next = kind switch
|
||||
{
|
||||
LegacyOperatorFolderKind.Design => _operatorSettings with
|
||||
{
|
||||
SceneDirectory = null
|
||||
},
|
||||
LegacyOperatorFolderKind.Resource => _operatorSettings with
|
||||
{
|
||||
ResourceDirectory = null
|
||||
},
|
||||
LegacyOperatorFolderKind.Background => _operatorSettings with
|
||||
{
|
||||
BackgroundDirectory = null
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
||||
};
|
||||
SaveOperatorSettings(
|
||||
next,
|
||||
$"{OperatorFolderLabel(kind)} 폴더를 기본값으로 되돌렸습니다. 앱을 다시 시작하면 적용됩니다.");
|
||||
}
|
||||
|
||||
private void SaveOperatorSettings(
|
||||
LegacyOperatorSettings next,
|
||||
string successMessage)
|
||||
{
|
||||
var result = _operatorSettingsStore.Save(next);
|
||||
if (!result.Succeeded || result.SavedSettings is null)
|
||||
{
|
||||
// The Web surface applies the navigation preference optimistically.
|
||||
// Advance the snapshot revision even though persistence failed so the
|
||||
// next state re-applies the still-authoritative native value.
|
||||
Interlocked.Increment(ref _operatorSettingsRevision);
|
||||
SetOperatorSettingsMessage(
|
||||
result.WarningMessage ?? "설정을 저장하지 못했습니다.",
|
||||
LegacyOperatorSettingsMessageKind.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_operatorSettings = result.SavedSettings;
|
||||
Interlocked.Increment(ref _operatorSettingsRevision);
|
||||
RefreshOperatorFolderSnapshots();
|
||||
SetOperatorSettingsMessage(
|
||||
successMessage,
|
||||
LegacyOperatorSettingsMessageKind.Success);
|
||||
}
|
||||
|
||||
private static bool CanReadSelectedDatabaseProfile(string resourceDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = new LegacyIniDatabaseOptionsLoader(_ => null).Load(
|
||||
Path.Combine(resourceDirectory, "MmoneyCoder.ini"));
|
||||
return true;
|
||||
}
|
||||
catch (DatabaseConfigurationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetOperatorSettingsMessage(
|
||||
string message,
|
||||
LegacyOperatorSettingsMessageKind kind)
|
||||
{
|
||||
_operatorSettingsMessage = message;
|
||||
_operatorSettingsMessageKind = kind;
|
||||
}
|
||||
|
||||
private void ReconcileRejectedOperatorSettingsIntent(
|
||||
LegacyUiIntent intent,
|
||||
string message)
|
||||
{
|
||||
if (!IsOperatorSettingsIntent(intent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReconcileRejectedOperatorSettingsIntent(message);
|
||||
}
|
||||
|
||||
private void ReconcileRejectedOperatorSettingsIntent(string message)
|
||||
{
|
||||
// Web applies the navigation switch optimistically. A new native revision
|
||||
// makes every early rejection re-project the persisted authoritative value.
|
||||
Interlocked.Increment(ref _operatorSettingsRevision);
|
||||
SetOperatorSettingsMessage(
|
||||
message,
|
||||
LegacyOperatorSettingsMessageKind.Warning);
|
||||
}
|
||||
|
||||
private void RefreshOperatorFolderSnapshots()
|
||||
{
|
||||
_designFolderSnapshot = CreateOperatorFolderSnapshot(
|
||||
LegacyOperatorSettingsFolderKind.Scene,
|
||||
_operatorSettings.SceneDirectory,
|
||||
"앱 기본 디자인 폴더",
|
||||
"필수 장면 확인됨");
|
||||
_resourceFolderSnapshot = CreateOperatorFolderSnapshot(
|
||||
LegacyOperatorSettingsFolderKind.Resource,
|
||||
_operatorSettings.ResourceDirectory,
|
||||
"앱 기본 설정 폴더",
|
||||
"UI 설정 검증됨");
|
||||
_backgroundFolderSnapshot = _operatorSettings.BackgroundDirectory is null
|
||||
? new LegacyOperatorFolderSnapshot(
|
||||
"장면 폴더 기준 자동 탐색",
|
||||
IsCustom: false,
|
||||
IsValid: true,
|
||||
"필요할 때 선택")
|
||||
: CreateOperatorFolderSnapshot(
|
||||
LegacyOperatorSettingsFolderKind.Background,
|
||||
_operatorSettings.BackgroundDirectory,
|
||||
"장면 폴더 기준 자동 탐색",
|
||||
"배경 폴더 확인됨");
|
||||
}
|
||||
|
||||
private static LegacyOperatorFolderSnapshot CreateOperatorFolderSnapshot(
|
||||
LegacyOperatorSettingsFolderKind kind,
|
||||
string? configuredPath,
|
||||
string defaultDisplay,
|
||||
string validStatus)
|
||||
{
|
||||
if (configuredPath is null)
|
||||
{
|
||||
var defaultPath = kind switch
|
||||
{
|
||||
LegacyOperatorSettingsFolderKind.Scene =>
|
||||
Path.Combine(AppContext.BaseDirectory, "Cuts"),
|
||||
LegacyOperatorSettingsFolderKind.Resource =>
|
||||
Path.Combine(AppContext.BaseDirectory, "Res"),
|
||||
_ => null
|
||||
};
|
||||
if (defaultPath is null ||
|
||||
!LegacyOperatorFolderValidator.TryValidate(
|
||||
kind,
|
||||
defaultPath,
|
||||
out _,
|
||||
out _))
|
||||
{
|
||||
return new LegacyOperatorFolderSnapshot(
|
||||
defaultDisplay,
|
||||
IsCustom: false,
|
||||
IsValid: false,
|
||||
"기본 폴더 확인 필요");
|
||||
}
|
||||
|
||||
return new LegacyOperatorFolderSnapshot(
|
||||
defaultDisplay,
|
||||
IsCustom: false,
|
||||
IsValid: true,
|
||||
validStatus);
|
||||
}
|
||||
|
||||
var validation = LegacyOperatorFolderValidator.Validate(kind, configuredPath);
|
||||
if (!validation.IsValid || validation.Folder is null)
|
||||
{
|
||||
return new LegacyOperatorFolderSnapshot(
|
||||
LegacyOperatorFolderValidator.AbbreviateForDisplay(configuredPath),
|
||||
IsCustom: true,
|
||||
IsValid: false,
|
||||
"폴더 확인 필요");
|
||||
}
|
||||
|
||||
var status = kind == LegacyOperatorSettingsFolderKind.Resource &&
|
||||
validation.Folder.DatabaseIniDetected
|
||||
? "UI·DB 설정 검증됨"
|
||||
: validStatus;
|
||||
return new LegacyOperatorFolderSnapshot(
|
||||
validation.Folder.AbbreviatedPath,
|
||||
IsCustom: true,
|
||||
IsValid: true,
|
||||
status);
|
||||
}
|
||||
|
||||
private LegacyOperatorSettingsSnapshot CreateOperatorSettingsSnapshot() => new(
|
||||
Volatile.Read(ref _operatorSettingsRevision),
|
||||
_designFolderSnapshot,
|
||||
_resourceFolderSnapshot,
|
||||
_backgroundFolderSnapshot,
|
||||
_operatorSettings.NavigationExpanded,
|
||||
RestartRequired: !OperatorFolderSettingsEqual(
|
||||
_operatorSettings,
|
||||
_appliedOperatorSettings),
|
||||
_operatorSettingsMessage,
|
||||
_operatorSettingsMessageKind,
|
||||
ResolveApplicationVersion(),
|
||||
ResolveDatabaseHealthLabel(
|
||||
Volatile.Read(ref _oracleDatabaseHealthState)),
|
||||
ResolveDatabaseHealthLabel(
|
||||
Volatile.Read(ref _mariaDatabaseHealthState)),
|
||||
DatabasePollingSeconds: (int)DatabaseHealthPollingInterval.TotalSeconds);
|
||||
|
||||
private string ResolveDatabaseHealthLabel(int rawState)
|
||||
{
|
||||
if (rawState < 0)
|
||||
{
|
||||
return _databaseRuntime is null ? "설정 안 됨" : "점검 대기";
|
||||
}
|
||||
|
||||
return (DatabaseHealthState)rawState switch
|
||||
{
|
||||
DatabaseHealthState.NotConfigured => "설정 안 됨",
|
||||
DatabaseHealthState.Healthy => "연결됨",
|
||||
DatabaseHealthState.Unhealthy => "연결 끊김",
|
||||
DatabaseHealthState.TimedOut => "응답 지연",
|
||||
_ => "확인 필요"
|
||||
};
|
||||
}
|
||||
|
||||
private static bool OperatorFolderSettingsEqual(
|
||||
LegacyOperatorSettings left,
|
||||
LegacyOperatorSettings right) =>
|
||||
string.Equals(
|
||||
left.SceneDirectory,
|
||||
right.SceneDirectory,
|
||||
StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(
|
||||
left.ResourceDirectory,
|
||||
right.ResourceDirectory,
|
||||
StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(
|
||||
left.BackgroundDirectory,
|
||||
right.BackgroundDirectory,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private static string ResolveApplicationVersion()
|
||||
{
|
||||
var version = typeof(MainWindow).Assembly.GetName().Version;
|
||||
return version is null
|
||||
? "확인 불가"
|
||||
: $"{version.Major}.{version.Minor}.{Math.Max(0, version.Build)}";
|
||||
}
|
||||
|
||||
private static LegacyOperatorSettingsFolderKind ToSettingsFolderKind(
|
||||
LegacyOperatorFolderKind kind) => kind switch
|
||||
{
|
||||
LegacyOperatorFolderKind.Design => LegacyOperatorSettingsFolderKind.Scene,
|
||||
LegacyOperatorFolderKind.Resource => LegacyOperatorSettingsFolderKind.Resource,
|
||||
LegacyOperatorFolderKind.Background => LegacyOperatorSettingsFolderKind.Background,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
||||
};
|
||||
|
||||
private static string OperatorFolderLabel(LegacyOperatorFolderKind kind) => kind switch
|
||||
{
|
||||
LegacyOperatorFolderKind.Design => "디자인",
|
||||
LegacyOperatorFolderKind.Resource => "설정",
|
||||
LegacyOperatorFolderKind.Background => "운영 배경",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user