zcbot/windows-node/Zcbot.WindowsNode/Presentation/ViewModels/DataRootPageViewModel.cs

133 lines
4.5 KiB
C#

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<string, string?>? ChooseTargetRequested;
internal event Func<string, bool>? ConfirmMigrationRequested;
internal event Func<string, Task<NodeDataMigrationResult>>? MigrationRequested;
internal event Action<NodeDataMigrationResult>? MigrationCompleted;
internal event Action<string>? 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";
}