465 lines
16 KiB
C#
465 lines
16 KiB
C#
using MBN_STOCK_WEBVIEW.Infrastructure;
|
|
using MBN_STOCK_WEBVIEW.LegacyApplication;
|
|
using MBN_STOCK_WEBVIEW.LegacyBridge;
|
|
using MMoneyCoderSharp.Data;
|
|
|
|
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
|
|
},
|
|
"저장됨");
|
|
break;
|
|
case LegacySetOperatorAppearanceIntent appearance:
|
|
SaveOperatorSettings(
|
|
_operatorSettings with
|
|
{
|
|
ColorTheme = appearance.ColorTheme,
|
|
ViewMode = appearance.ViewMode,
|
|
StartWorkspace = appearance.StartWorkspace
|
|
},
|
|
"저장됨");
|
|
break;
|
|
default:
|
|
SetOperatorSettingsMessage(
|
|
"지원하지 않는 설정 요청입니다.",
|
|
LegacyOperatorSettingsMessageKind.Error);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private async Task ChooseOperatorFolderAsync(
|
|
LegacyOperatorFolderKind kind,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
try
|
|
{
|
|
var initialDirectory = ResolveOperatorFolderPickerStartDirectory(kind);
|
|
var selectedPath = await PickOperatorFolderAsync(
|
|
initialDirectory,
|
|
ResolveOperatorFolderPickerTitle(kind));
|
|
if (selectedPath is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
var validationKind = ToSettingsFolderKind(kind);
|
|
if (!LegacyOperatorFolderValidator.TryValidate(
|
|
validationKind,
|
|
selectedPath,
|
|
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, "저장됨 · 재시작 필요");
|
|
}
|
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
throw;
|
|
}
|
|
catch
|
|
{
|
|
SetOperatorSettingsMessage(
|
|
"폴더 선택 창을 열거나 선택 결과를 저장하지 못했습니다.",
|
|
LegacyOperatorSettingsMessageKind.Error);
|
|
}
|
|
}
|
|
|
|
private string? ResolveOperatorFolderPickerStartDirectory(
|
|
LegacyOperatorFolderKind kind)
|
|
{
|
|
var preferredPath = kind switch
|
|
{
|
|
LegacyOperatorFolderKind.Design =>
|
|
_operatorSettings.SceneDirectory ??
|
|
Path.Combine(AppContext.BaseDirectory, "Cuts"),
|
|
LegacyOperatorFolderKind.Resource =>
|
|
_operatorSettings.ResourceDirectory ??
|
|
Path.Combine(AppContext.BaseDirectory, "Res"),
|
|
LegacyOperatorFolderKind.Background =>
|
|
_operatorSettings.BackgroundDirectory ??
|
|
ResolveAutomaticBackgroundDirectory(),
|
|
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
|
};
|
|
|
|
return FindNearestExistingDirectory(preferredPath) ??
|
|
FindNearestExistingDirectory(AppContext.BaseDirectory);
|
|
}
|
|
|
|
private string ResolveAutomaticBackgroundDirectory()
|
|
{
|
|
var sceneDirectory = _operatorSettings.SceneDirectory ??
|
|
Path.Combine(AppContext.BaseDirectory, "Cuts");
|
|
var canonicalSceneDirectory = Path.TrimEndingDirectorySeparator(
|
|
Path.GetFullPath(sceneDirectory));
|
|
var sceneParent = Path.GetDirectoryName(canonicalSceneDirectory);
|
|
return Path.Combine(
|
|
sceneParent ?? AppContext.BaseDirectory,
|
|
"배경");
|
|
}
|
|
|
|
private static string? FindNearestExistingDirectory(string? path)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(path) ||
|
|
!Path.IsPathFullyQualified(path))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
for (var candidate = new DirectoryInfo(Path.GetFullPath(path));
|
|
candidate is not null;
|
|
candidate = candidate.Parent)
|
|
{
|
|
if (candidate.Exists)
|
|
{
|
|
return candidate.FullName;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception exception) when (
|
|
exception is ArgumentException or
|
|
IOException or
|
|
NotSupportedException or
|
|
System.Security.SecurityException or
|
|
UnauthorizedAccessException)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string ResolveOperatorFolderPickerTitle(
|
|
LegacyOperatorFolderKind kind) => kind switch
|
|
{
|
|
LegacyOperatorFolderKind.Design => "폴더 선택 - 디자인",
|
|
LegacyOperatorFolderKind.Resource => "폴더 선택 - 설정",
|
|
LegacyOperatorFolderKind.Background => "폴더 선택 - 배경",
|
|
_ => throw new ArgumentOutOfRangeException(nameof(kind))
|
|
};
|
|
|
|
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,
|
|
"초기화됨 · 재시작 필요");
|
|
}
|
|
|
|
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,
|
|
"앱 기본값");
|
|
_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)
|
|
{
|
|
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 var defaultFolder,
|
|
out _) ||
|
|
defaultFolder is null)
|
|
{
|
|
return new LegacyOperatorFolderSnapshot(
|
|
defaultDisplay,
|
|
IsCustom: false,
|
|
IsValid: false,
|
|
"기본 폴더 확인 필요");
|
|
}
|
|
|
|
return new LegacyOperatorFolderSnapshot(
|
|
defaultDisplay,
|
|
IsCustom: false,
|
|
IsValid: true,
|
|
"사용 가능");
|
|
}
|
|
|
|
var validation = LegacyOperatorFolderValidator.Validate(kind, configuredPath);
|
|
if (!validation.IsValid || validation.Folder is null)
|
|
{
|
|
return new LegacyOperatorFolderSnapshot(
|
|
LegacyOperatorFolderValidator.AbbreviateForDisplay(configuredPath),
|
|
IsCustom: true,
|
|
IsValid: false,
|
|
"폴더 확인 필요");
|
|
}
|
|
|
|
return new LegacyOperatorFolderSnapshot(
|
|
validation.Folder.AbbreviatedPath,
|
|
IsCustom: true,
|
|
IsValid: true,
|
|
"사용 가능");
|
|
}
|
|
|
|
private LegacyOperatorSettingsSnapshot CreateOperatorSettingsSnapshot() => new(
|
|
Volatile.Read(ref _operatorSettingsRevision),
|
|
_designFolderSnapshot,
|
|
_resourceFolderSnapshot,
|
|
_backgroundFolderSnapshot,
|
|
_operatorSettings.NavigationExpanded,
|
|
_operatorSettings.ColorTheme,
|
|
_operatorSettings.ViewMode,
|
|
_operatorSettings.StartWorkspace,
|
|
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))
|
|
};
|
|
|
|
}
|