using System.ComponentModel; namespace FluidExplorer.Services.Operations; public enum FileOperationKind { Copy, Move, Delete, Recycle, Rename, NewFolder } public enum JobState { Queued, Running, Paused, Completed, CompletedWithErrors, Cancelled, Failed } public enum ConflictPolicy { /// 交给 UI 决定(ConflictResolver)。 Ask, Replace, Skip, KeepBoth, Merge } public enum ConflictResolution { Replace, Skip, KeepBoth, Cancel } public sealed class ConflictInfo { public required string SourcePath { get; init; } public required string DestinationPath { get; init; } public bool SourceIsDirectory { get; init; } public long SourceSize { get; init; } public long DestinationSize { get; init; } public DateTime SourceModifiedUtc { get; init; } public DateTime DestinationModifiedUtc { get; init; } /// true 表示用户勾选了"为后续所有冲突执行相同操作"。 public bool ApplyToAll { get; set; } } /// 队列里的一个作业:可暂停/继续/取消,进度实时上报(含速度与剩余时间)。 public sealed class FileOperationJob : INotifyPropertyChanged { private JobState _state = JobState.Queued; private int _completedItems; private long _completedBytes; private string? _currentItem; private string? _error; private double _bytesPerSecond; private bool _isIndeterminate; public Guid Id { get; } = Guid.NewGuid(); public required FileOperationKind Kind { get; init; } public required string Title { get; init; } public required IReadOnlyList Sources { get; init; } public string? Destination { get; init; } public ConflictPolicy Policy { get; init; } = ConflictPolicy.Ask; public bool PermanentDelete { get; init; } public string? NewName { get; init; } public DateTime StartedUtc { get; } = DateTime.UtcNow; public JobState State { get => _state; set => Set(ref _state, value); } public int TotalItems { get; set; } public int CompletedItems { get => _completedItems; set => Set(ref _completedItems, value); } public long TotalBytes { get; set; } public long CompletedBytes { get => _completedBytes; set => Set(ref _completedBytes, value); } public double BytesPerSecond { get => _bytesPerSecond; set => Set(ref _bytesPerSecond, value); } public string? CurrentItem { get => _currentItem; set => Set(ref _currentItem, value); } public string? Error { get => _error; set => Set(ref _error, value); } /// 大小为 0 的作业(纯重命名等)显示旋转指示器。 public bool IsIndeterminate { get => _isIndeterminate; set => Set(ref _isIndeterminate, value); } public double Progress => TotalBytes > 0 ? Math.Clamp((double)CompletedBytes / TotalBytes, 0, 1) : (TotalItems > 0 ? Math.Clamp((double)CompletedItems / TotalItems, 0, 1) : 0); public TimeSpan? Eta => BytesPerSecond > 1 && TotalBytes > CompletedBytes ? TimeSpan.FromSeconds((TotalBytes - CompletedBytes) / BytesPerSecond) : null; public bool CanPause => State is JobState.Running or JobState.Paused; public bool CanCancel => State is JobState.Queued or JobState.Running or JobState.Paused; public bool IsFinished => State is JobState.Completed or JobState.CompletedWithErrors or JobState.Cancelled or JobState.Failed; public event PropertyChangedEventHandler? PropertyChanged; internal void RaiseAll() { foreach (var name in new[] { nameof(State), nameof(CompletedItems), nameof(CompletedBytes), nameof(BytesPerSecond), nameof(CurrentItem), nameof(Error), nameof(Progress), nameof(Eta), nameof(CanPause), nameof(CanCancel), nameof(IsFinished), nameof(IsIndeterminate) }) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } private void Set(ref T field, T value, [System.Runtime.CompilerServices.CallerMemberName] string? name = null) { if (EqualityComparer.Default.Equals(field, value)) return; field = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } } public sealed class UndoEntry { public required string Description { get; init; } public required FileOperationKind Kind { get; init; } /// (原路径, 现路径) 对;撤销即反向搬运。 public required List<(string From, string To)> Moves { get; init; } /// 删除操作记录:回收站里的 $R 文件路径 → 原始路径。 public List<(string RecyclePath, string OriginalPath)> Deleted { get; init; } = []; } public sealed record OperationResult(bool Success, int Succeeded, int Failed, int Skipped, string? Error = null); /// /// 文件操作引擎:所有操作进入统一队列串行/并行执行,UI 永不阻塞。 /// 支持暂停、继续、取消、冲突策略、错误重试,以及一步撤销(Ctrl+Z)。 /// public interface IFileOperationService { IReadOnlyList Jobs { get; } event EventHandler? JobsChanged; /// UI 设置此回调以弹出冲突对话框;未设置时按 KeepBoth 处理。 Func>? ConflictResolver { get; set; } FileOperationJob EnqueueCopy(IReadOnlyList sources, string destination, ConflictPolicy policy = ConflictPolicy.Ask); FileOperationJob EnqueueMove(IReadOnlyList sources, string destination, ConflictPolicy policy = ConflictPolicy.Ask); FileOperationJob EnqueueDelete(IReadOnlyList paths, bool permanent = false); FileOperationJob EnqueueRename(string path, string newName); FileOperationJob EnqueueNewFolder(string parentDirectory, string name); void Pause(Guid jobId); void Resume(Guid jobId); void Cancel(Guid jobId); void ClearFinished(); bool CanUndo { get; } string? UndoDescription { get; } event EventHandler? UndoStackChanged; Task UndoAsync(CancellationToken cancellationToken = default); /// 计算源集合的总大小与条目数(后台执行,用于进度条与冲突提示)。 Task<(long Bytes, int Items)> MeasureAsync(IReadOnlyList paths, CancellationToken cancellationToken); }