using System.Diagnostics; using System.IO.Enumeration; using FluidExplorer.Models; namespace FluidExplorer.Services.FileSystem; /// /// 资源管理器最核心的性能路径:目录枚举。 /// 使用 .NET 的 FileSystemEnumerable(底层为 NtQueryDirectoryFile + 大缓冲批量返回), /// 一次调用即可拿到名称/属性/大小/时间,比 DirectoryInfo 逐文件查询快一个数量级。 /// public sealed class FastFileSystemService : IFileSystemService { private static readonly EnumerationOptions Options = new() { RecurseSubdirectories = false, IgnoreInaccessible = true, AttributesToSkip = 0, // 隐藏/系统文件也枚举出来,由界面决定是否显示 ReturnSpecialDirectories = false, MatchType = MatchType.Simple, BufferSize = 0 // 0 = 使用平台默认的大缓冲区 }; public bool DirectoryExists(string path) { try { return Directory.Exists(path); } catch { return false; } } public bool FileExists(string path) { try { return File.Exists(path); } catch { return false; } } /// /// 分批异步枚举:首批(batchSize 条)会尽快回调,让界面立刻有内容; /// 整个枚举可取消,不会阻塞调用线程。 /// public async Task EnumerateAsync( string path, FolderListing listing, Func, Task> onBatch, int batchSize = 256, CancellationToken cancellationToken = default) { listing.Path = path; listing.IsLoading = true; listing.IsComplete = false; listing.Error = null; try { await Task.Run(async () => { var buffer = new List(batchSize); var sw = Stopwatch.StartNew(); foreach (var entry in EnumerateCore(path, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); buffer.Add(entry); if (buffer.Count >= batchSize) { await onBatch(buffer).ConfigureAwait(false); buffer = new List(batchSize); } } if (buffer.Count > 0) await onBatch(buffer).ConfigureAwait(false); _ = sw.Elapsed; }, cancellationToken).ConfigureAwait(false); listing.IsComplete = true; } catch (OperationCanceledException) { listing.IsComplete = false; } catch (UnauthorizedAccessException ex) { listing.Error = $"没有访问权限:{ex.Message}"; } catch (DirectoryNotFoundException) { listing.Error = "文件夹不存在或已被移动。"; } catch (IOException ex) { // 网络路径断开、设备未就绪等:给出可读提示而不是抛出 listing.Error = $"无法读取此位置:{ex.Message}"; } finally { listing.IsLoading = false; } } public IReadOnlyList Enumerate(string path, bool includeHidden = true) { var list = new List(256); foreach (var e in EnumerateCore(path, CancellationToken.None)) { if (!includeHidden && e.IsHidden) continue; list.Add(e); } return list; } /// 核心枚举:单次系统调用序列,无逐文件 stat。 private static IEnumerable EnumerateCore(string path, CancellationToken cancellationToken) { var normalized = PathHelper.NormalizeForApi(path); var enumerable = new FileSystemEnumerable( normalized, static (ref FileSystemEntry entry) => new FileEntry { Name = entry.FileName.ToString(), FullPath = entry.ToFullPath(), IsDirectory = entry.IsDirectory, Size = entry.IsDirectory ? 0 : entry.Length, ModifiedUtc = entry.LastWriteTimeUtc.UtcDateTime, CreatedUtc = entry.CreationTimeUtc.UtcDateTime, AccessedUtc = entry.LastAccessTimeUtc.UtcDateTime, Attributes = entry.Attributes }, Options); foreach (var item in enumerable) { cancellationToken.ThrowIfCancellationRequested(); yield return item; } } /// 递归求文件夹大小:并行遍历 + 可取消,用于属性对话框与状态栏提示。 public Task GetDirectorySizeAsync(string path, CancellationToken cancellationToken) => Task.Run(() => { long total = 0; var stack = new Stack(); stack.Push(path); while (stack.Count > 0) { cancellationToken.ThrowIfCancellationRequested(); var dir = stack.Pop(); try { foreach (var file in Directory.EnumerateFiles(dir)) { try { total += new FileInfo(file).Length; } catch { /* 跳过无法访问的项 */ } } foreach (var sub in Directory.EnumerateDirectories(dir)) { var info = new DirectoryInfo(sub); if ((info.Attributes & FileAttributes.ReparsePoint) != 0) continue; // 不跟随链接,防止环 stack.Push(sub); } } catch { /* 跳过无权限目录 */ } } return total; }, cancellationToken); } /// 路径规范化统一入口(长路径、去掉尾部分隔符、避免重复分隔符)。 public static class PathHelper { public const string ExtendedPrefix = @"\\?\"; public const string ExtendedUncPrefix = @"\\?\UNC\"; /// 用于 Win32/文件系统 API 的路径(超长路径自动加 \\?\ 前缀)。 public static string NormalizeForApi(string path) { if (string.IsNullOrEmpty(path)) return path; if (path.StartsWith(ExtendedPrefix, StringComparison.Ordinal)) return path; path = path.Replace('/', '\\'); // 去掉重复分隔符(保留 UNC 开头的两个) while (path.Contains(@"\\") && !path.StartsWith(@"\\", StringComparison.Ordinal)) path = path.Replace(@"\\", @"\"); if (path.Length >= 248) { return path.StartsWith(@"\\", StringComparison.Ordinal) ? ExtendedUncPrefix + path[2..] : ExtendedPrefix + path; } return path; } /// 去掉 \\?\ 前缀,用于显示。 public static string StripExtendedPrefix(string path) { if (path.StartsWith(ExtendedUncPrefix, StringComparison.Ordinal)) return @"\\" + path[ExtendedUncPrefix.Length..]; if (path.StartsWith(ExtendedPrefix, StringComparison.Ordinal)) return path[ExtendedPrefix.Length..]; return path; } /// 规范化显示路径:统一分隔符、去掉末尾分隔符(根目录除外)。 public static string NormalizeDisplay(string path) { if (string.IsNullOrEmpty(path)) return path; var p = StripExtendedPrefix(path).Replace('/', '\\'); if (p.Length > 3 && p.EndsWith('\\')) p = p.TrimEnd('\\'); return p; } public static string GetParent(string path) { var p = NormalizeDisplay(path); if (p.Length <= 3) return p; // "C:\" 的父级还是自己 var idx = p.LastIndexOf('\\'); if (idx < 0) return p; var parent = p[..idx]; if (parent.Length == 2 && parent[1] == ':') parent += "\\"; return parent.Length == 0 ? p : parent; } public static string GetName(string path) { var p = NormalizeDisplay(path); if (p.Length <= 3) return p; var idx = p.LastIndexOf('\\'); return idx < 0 ? p : p[(idx + 1)..]; } }