using System.Security; using System.Windows.Input; namespace Zcbot.WindowsNode; internal sealed class DataRootPageViewModel : ObservableObject { private readonly IDataRootManagementService service; private readonly AsyncCommand changeCommand; private NodeDataRootSelection selection; private string status; private bool isMigrating; internal DataRootPageViewModel(IDataRootManagementService service) { this.service = service; selection = service.ReadSelection(); status = SelectionStatus(selection); changeCommand = new AsyncCommand(ChangeAsync, () => !IsManaged && !IsMigrating); ChangeCommand = changeCommand; OpenCommand = new RelayCommand(() => OpenRequested?.Invoke(CurrentRoot)); } internal event Func? ChooseTargetRequested; internal event Func? ConfirmMigrationRequested; internal event Func>? MigrationRequested; internal event Action? MigrationCompleted; internal event Action? OpenRequested; public string CurrentRoot => selection.RootDirectory; public bool IsManaged => selection.IsEnvironmentManaged; public bool IsMigrating { get => isMigrating; private set { if (SetProperty(ref isMigrating, value)) { changeCommand.RaiseCanExecuteChanged(); } } } public string Status { get => status; private set => SetProperty(ref status, value); } public ICommand ChangeCommand { get; } public ICommand OpenCommand { get; } internal void Refresh() { selection = service.ReadSelection(); OnPropertyChanged(nameof(CurrentRoot)); OnPropertyChanged(nameof(IsManaged)); Status = SelectionStatus(selection); changeCommand.RaiseCanExecuteChanged(); } private async Task ChangeAsync() { if (MigrationRequested is null) return; try { if (service.HasPendingJobs) { Status = "本机仍有未完成任务,暂不能迁移数据目录。请等待任务结束后重试。"; return; } } catch (Exception exception) when ( exception is IOException or UnauthorizedAccessException) { Status = $"无法确认本机任务状态,数据目录不会迁移:{exception.Message}"; return; } var selected = ChooseTargetRequested?.Invoke(CurrentRoot); if (string.IsNullOrWhiteSpace(selected)) return; string target; try { target = service.Normalize(selected); } catch (NodeConfigurationException exception) { Status = exception.Message; return; } if (string.Equals(target, CurrentRoot, StringComparison.OrdinalIgnoreCase)) { Status = "所选目录与当前数据目录相同。"; return; } if (ConfirmMigrationRequested?.Invoke(target) != true) return; IsMigrating = true; Status = "正在停止调度并复制、校验数据,请勿退出 Node…"; try { var result = await MigrationRequested(target); Status = $"迁移完成:{result.FileCount} 个文件,{FormatBytes(result.TotalBytes)}。正在重启 Node…"; MigrationCompleted?.Invoke(result); } catch (Exception exception) when ( exception is IOException or NodeConfigurationException or SecurityException or UnauthorizedAccessException) { IsMigrating = false; Refresh(); Status = $"数据目录未切换,原目录继续有效:{exception.Message}"; } } private static string SelectionStatus(NodeDataRootSelection value) => value.IsEnvironmentManaged ? $"由机器环境变量 {NodeDataRootSettings.EnvironmentVariableName} 管理,界面不可修改。" : value.Source == "user" ? "使用当前 Windows 专用账号保存的自定义目录。" : "使用默认目录。"; private static string FormatBytes(long bytes) => bytes >= 1024L * 1024 * 1024 ? $"{bytes / (1024d * 1024 * 1024):F2} GiB" : bytes >= 1024L * 1024 ? $"{bytes / (1024d * 1024):F1} MiB" : $"{bytes / 1024d:F1} KiB"; }