Initial commit: FluidExplorer:从零实现的 WinUI 3 文件资源管理器替代品(Mica、命令栏、面包屑)
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
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
|
||||
{
|
||||
/// <summary>交给 UI 决定(ConflictResolver)。</summary>
|
||||
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; }
|
||||
/// <summary>true 表示用户勾选了"为后续所有冲突执行相同操作"。</summary>
|
||||
public bool ApplyToAll { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>队列里的一个作业:可暂停/继续/取消,进度实时上报(含速度与剩余时间)。</summary>
|
||||
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<string> 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); }
|
||||
|
||||
/// <summary>大小为 0 的作业(纯重命名等)显示旋转指示器。</summary>
|
||||
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<T>(ref T field, T value, [System.Runtime.CompilerServices.CallerMemberName] string? name = null)
|
||||
{
|
||||
if (EqualityComparer<T>.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; }
|
||||
/// <summary>(原路径, 现路径) 对;撤销即反向搬运。</summary>
|
||||
public required List<(string From, string To)> Moves { get; init; }
|
||||
/// <summary>删除操作记录:回收站里的 $R 文件路径 → 原始路径。</summary>
|
||||
public List<(string RecyclePath, string OriginalPath)> Deleted { get; init; } = [];
|
||||
}
|
||||
|
||||
public sealed record OperationResult(bool Success, int Succeeded, int Failed, int Skipped, string? Error = null);
|
||||
|
||||
/// <summary>
|
||||
/// 文件操作引擎:所有操作进入统一队列串行/并行执行,UI 永不阻塞。
|
||||
/// 支持暂停、继续、取消、冲突策略、错误重试,以及一步撤销(Ctrl+Z)。
|
||||
/// </summary>
|
||||
public interface IFileOperationService
|
||||
{
|
||||
IReadOnlyList<FileOperationJob> Jobs { get; }
|
||||
event EventHandler? JobsChanged;
|
||||
|
||||
/// <summary>UI 设置此回调以弹出冲突对话框;未设置时按 KeepBoth 处理。</summary>
|
||||
Func<ConflictInfo, Task<ConflictResolution>>? ConflictResolver { get; set; }
|
||||
|
||||
FileOperationJob EnqueueCopy(IReadOnlyList<string> sources, string destination, ConflictPolicy policy = ConflictPolicy.Ask);
|
||||
FileOperationJob EnqueueMove(IReadOnlyList<string> sources, string destination, ConflictPolicy policy = ConflictPolicy.Ask);
|
||||
FileOperationJob EnqueueDelete(IReadOnlyList<string> 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<OperationResult> UndoAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>计算源集合的总大小与条目数(后台执行,用于进度条与冲突提示)。</summary>
|
||||
Task<(long Bytes, int Items)> MeasureAsync(IReadOnlyList<string> paths, CancellationToken cancellationToken);
|
||||
}
|
||||
Reference in New Issue
Block a user