Files
MBN_STOCK_WEBVIEW/src/MBN_STOCK_WEBVIEW.LegacyParityApp/MainWindow.Settings.cs

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))
};
}