Files

110 lines
4.3 KiB
C#

using System.Buffers.Binary;
using System.Text;
namespace FluidExplorer.Services.Shell;
/// <summary>回收站里的一条记录(从 $I 元数据文件解析,含原始路径)。</summary>
public sealed record RecycleBinEntry(
string RecyclePath,
string OriginalPath,
long Size,
DateTime DeletedUtc,
string VolumeRoot)
{
public string Name => Services.FileSystem.PathHelper.GetName(OriginalPath);
public string OriginalDirectory => Services.FileSystem.PathHelper.GetParent(OriginalPath);
public bool IsDirectory => Size == 0 && OriginalPath.Length > 0 && !Path.HasExtension(OriginalPath);
}
/// <summary>
/// 直接读取各卷的 $Recycle.Bin\&lt;SID&gt;\$I* 元数据文件,得到回收站内容与原始路径。
/// 这样"还原/彻底删除"都能由本程序完成(不依赖系统弹窗),也支持一步撤销。
/// </summary>
public static class RecycleBinView
{
public static async Task<IReadOnlyList<RecycleBinEntry>> EnumerateAsync(CancellationToken cancellationToken)
=> await Task.Run(() => Enumerate(cancellationToken), cancellationToken).ConfigureAwait(false);
public static IReadOnlyList<RecycleBinEntry> Enumerate(CancellationToken cancellationToken = default)
{
var results = new List<RecycleBinEntry>();
foreach (var drive in DriveInfo.GetDrives())
{
cancellationToken.ThrowIfCancellationRequested();
try
{
if (!drive.IsReady || drive.DriveType != DriveType.Fixed) continue;
var binRoot = Path.Combine(drive.Name, "$Recycle.Bin");
if (!Directory.Exists(binRoot)) continue;
foreach (var sidDir in Directory.EnumerateDirectories(binRoot))
{
cancellationToken.ThrowIfCancellationRequested();
try
{
foreach (var metaFile in Directory.EnumerateFiles(sidDir, "$I*"))
{
cancellationToken.ThrowIfCancellationRequested();
var entry = TryParse(metaFile, drive.Name);
if (entry is not null) results.Add(entry);
}
}
catch
{
// 其他用户的回收站目录通常无权限,跳过
}
}
}
catch
{
// 卷不可读时跳过
}
}
return results;
}
/// <summary>解析单个 $I 元数据文件。</summary>
public static RecycleBinEntry? TryParse(string metaFilePath, string volumeRoot)
{
try
{
var bytes = File.ReadAllBytes(metaFilePath);
if (bytes.Length < 24) return null;
var version = BinaryPrimitives.ReadInt64LittleEndian(bytes.AsSpan(0, 8));
var size = BinaryPrimitives.ReadInt64LittleEndian(bytes.AsSpan(8, 8));
var fileTime = BinaryPrimitives.ReadInt64LittleEndian(bytes.AsSpan(16, 8));
var deleted = fileTime > 0 ? DateTime.FromFileTimeUtc(fileTime) : DateTime.MinValue;
string originalPath;
if (version >= 2 && bytes.Length >= 28)
{
var length = BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(24, 4));
if (length <= 0 || 28 + length * 2 > bytes.Length) return null;
originalPath = Encoding.Unicode.GetString(bytes, 28, length * 2).TrimEnd('\0');
}
else if (version == 1)
{
// 旧格式:路径固定在 0x2C 偏移处的 260 个宽字符
if (bytes.Length < 0x2C + 520) return null;
originalPath = Encoding.Unicode.GetString(bytes, 0x2C, 520).TrimEnd('\0');
}
else
{
return null;
}
if (string.IsNullOrWhiteSpace(originalPath)) return null;
var fileName = Path.GetFileName(metaFilePath);
var recyclePath = Path.Combine(Path.GetDirectoryName(metaFilePath)!, "$R" + fileName[2..]);
return new RecycleBinEntry(recyclePath, originalPath, Math.Max(0, size), deleted, volumeRoot);
}
catch
{
return null;
}
}
}