Files
Explorer-Workbench/src/Explorer.Presentation/ViewModels/FolderSyncViewModel.cs

229 lines
6.4 KiB
C#

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Explorer.Domain;
using Explorer.FileOperations;
namespace Explorer.Presentation.ViewModels;
public sealed partial class FolderSyncViewModel : ObservableObject
{
private readonly FolderSyncService _sync;
private OperationPlan? _plan;
[ObservableProperty] private SyncProfile? _selected;
[ObservableProperty] private string _name = "Photos backup";
[ObservableProperty] private string _sourcePath = "";
[ObservableProperty] private string _destPath = "";
[ObservableProperty] private SyncMode _mode = SyncMode.CopyUpdate;
[ObservableProperty] private string _excludes = "";
[ObservableProperty] private bool _autoRun;
[ObservableProperty] private string _status = "Save a profile, then Preview.";
[ObservableProperty] private bool _canQueue;
public FolderSyncViewModel(FolderSyncService sync)
{
_sync = sync;
Profiles = [];
Rows = [];
Modes =
[
new SyncModeOption("Copy / Update", SyncMode.CopyUpdate),
new SyncModeOption("Mirror", SyncMode.Mirror)
];
}
public ObservableCollection<SyncProfile> Profiles { get; }
public ObservableCollection<SyncPreviewRow> Rows { get; }
public IReadOnlyList<SyncModeOption> Modes { get; }
public bool AutoRunEnabled => Mode == SyncMode.CopyUpdate;
public async Task LoadAsync()
{
Profiles.Clear();
foreach (var profile in await _sync.ListAsync().ConfigureAwait(true))
{
Profiles.Add(profile);
}
if (Profiles.Count > 0)
{
Selected = Profiles[0];
}
else
{
NewProfile();
}
}
partial void OnSelectedChanged(SyncProfile? value)
{
if (value is null)
{
return;
}
Name = value.Name;
SourcePath = value.SourcePath;
DestPath = value.DestPath;
Mode = value.Mode;
Excludes = value.Excludes;
AutoRun = value.AutoRun && value.Mode == SyncMode.CopyUpdate;
ClearPlan("Profile loaded. Preview to see what would change.");
}
partial void OnModeChanged(SyncMode value)
{
OnPropertyChanged(nameof(AutoRunEnabled));
if (value != SyncMode.CopyUpdate)
{
AutoRun = false;
}
}
[RelayCommand]
public void NewProfile()
{
Selected = null;
Name = "New sync";
SourcePath = "";
DestPath = "";
Mode = SyncMode.CopyUpdate;
Excludes = "";
AutoRun = false;
ClearPlan("New profile. Choose folders and Save.");
}
[RelayCommand]
public async Task SaveAsync()
{
if (string.IsNullOrWhiteSpace(SourcePath) || string.IsNullOrWhiteSpace(DestPath))
{
Status = "Choose a source folder and a destination folder.";
return;
}
var profile = CurrentProfile();
profile.Id = await _sync.SaveAsync(profile).ConfigureAwait(true);
var existing = Profiles.FirstOrDefault(p => p.Id == profile.Id);
if (existing is not null)
{
var index = Profiles.IndexOf(existing);
Profiles[index] = profile;
}
else
{
Profiles.Add(profile);
}
Selected = profile;
Status = "Profile saved.";
}
[RelayCommand]
public async Task DeleteAsync()
{
if (Selected is null || Selected.Id <= 0)
{
NewProfile();
return;
}
await _sync.DeleteAsync(Selected.Id).ConfigureAwait(true);
Profiles.Remove(Selected);
if (Profiles.Count > 0)
{
Selected = Profiles[0];
}
else
{
NewProfile();
}
Status = "Profile removed.";
}
[RelayCommand]
public async Task AnalyzeAsync()
{
var profile = CurrentProfile();
Status = "Analyzing…";
CanQueue = false;
var plan = await Task.Run(() => _sync.PreviewAsync(profile)).ConfigureAwait(true);
ApplyPlan(plan);
}
[RelayCommand]
public async Task QueueAsync()
{
if (_plan is null || !_plan.CanEnqueue)
{
Status = "Preview first. Nothing to queue.";
return;
}
var profile = CurrentProfile();
if (profile.Id <= 0)
{
await SaveAsync().ConfigureAwait(true);
profile = CurrentProfile();
}
var plan = await _sync.EnqueueAsync(profile, _plan).ConfigureAwait(true);
ApplyPlan(plan);
if (plan.CanEnqueue)
{
Status = profile.LastStatus ?? "Queued.";
CanQueue = false;
}
}
public SyncProfile CurrentProfile()
=> new()
{
Id = Selected?.Id ?? 0,
Name = string.IsNullOrWhiteSpace(Name) ? "Sync" : Name.Trim(),
SourcePath = SourcePath.Trim(),
DestPath = DestPath.Trim(),
Mode = Mode,
Excludes = Excludes ?? "",
AutoRun = AutoRun && Mode == SyncMode.CopyUpdate,
SourceVolumeGuid = Selected?.SourceVolumeGuid,
DestVolumeGuid = Selected?.DestVolumeGuid,
CreatedUtc = Selected?.CreatedUtc ?? DateTimeOffset.UtcNow,
LastRunUtc = Selected?.LastRunUtc,
LastStatus = Selected?.LastStatus
};
private void ApplyPlan(OperationPlan plan)
{
_plan = plan;
Rows.Clear();
foreach (var row in plan.SyncPreview)
{
Rows.Add(row);
}
CanQueue = plan.CanEnqueue;
var errors = plan.Issues.Count(i => i.Severity == PlanIssueSeverity.Error);
var warnings = plan.Issues.Count(i => i.Severity == PlanIssueSeverity.Warning);
Status = errors > 0
? plan.Issues.First(i => i.Severity == PlanIssueSeverity.Error).Message
: plan.Operations.Count == 0
? warnings > 0
? $"Nothing to queue · {warnings} skipped"
: "Folders already match."
: $"{plan.Operations.Count} operations · {warnings} skipped";
}
private void ClearPlan(string status)
{
_plan = null;
Rows.Clear();
CanQueue = false;
Status = status;
}
}
public sealed record SyncModeOption(string Label, SyncMode Mode);