11import 'dart:async' ;
2+ import 'dart:io' ;
23
34import 'package:file_selector/file_selector.dart' as fs;
45
6+ import '../logger/app_logger.dart' ;
7+
8+ /// 异步检查目录是否存在,并限制等待时间。
9+ ///
10+ /// 不使用 [Directory.existsSync] ,避免离线网络盘、云盘占位目录或失效
11+ /// Junction 在 Flutter UI isolate 上同步阻塞。
12+ Future <bool > directoryExistsSafely (
13+ String path, {
14+ Duration timeout = const Duration (seconds: 2 ),
15+ }) async {
16+ if (path.trim ().isEmpty) return false ;
17+ final stopwatch = Stopwatch ()..start ();
18+ try {
19+ final exists = await Directory (path).exists ().timeout (timeout);
20+ stopwatch.stop ();
21+ AppLogger .instance.log (
22+ '[DirectoryPicker] 路径检查完成: exists=$exists , '
23+ 'elapsed=${stopwatch .elapsedMilliseconds }ms, path=$path ' ,
24+ );
25+ return exists;
26+ } on TimeoutException {
27+ stopwatch.stop ();
28+ AppLogger .instance.log (
29+ '[DirectoryPicker] 路径检查超时: '
30+ 'elapsed=${stopwatch .elapsedMilliseconds }ms, path=$path ' ,
31+ );
32+ return false ;
33+ } catch (e) {
34+ stopwatch.stop ();
35+ AppLogger .instance.log (
36+ '[DirectoryPicker] 路径检查异常: $e , '
37+ 'elapsed=${stopwatch .elapsedMilliseconds }ms, path=$path ' ,
38+ );
39+ return false ;
40+ }
41+ }
42+
43+ /// 返回可安全传给原生选择器的初始目录。
44+ /// 无效或慢路径返回 null,避免 Windows 恢复到失效网络位置。
45+ Future <String ?> validInitialDirectory (String ? candidate) async {
46+ if (candidate == null || candidate.trim ().isEmpty) return null ;
47+ return await directoryExistsSafely (candidate) ? candidate : null ;
48+ }
49+
550/// 统一的目录选择封装。
651///
752/// 历史问题:`file_picker` 的目录选择(底层 comdlg32)在 Windows 上会**原生崩溃**,
@@ -19,16 +64,43 @@ Future<String?> pickDirectory({
1964 String ? initialDirectory,
2065 Duration timeout = const Duration (seconds: 60 ),
2166}) async {
67+ final stopwatch = Stopwatch ()..start ();
68+ AppLogger .instance.log (
69+ '[DirectoryPicker] 开始调用原生选择器: '
70+ 'initial=${initialDirectory ?? "(系统默认)" }, timeout=${timeout .inSeconds }s' ,
71+ );
2272 try {
2373 final path = await fs
2474 .getDirectoryPath (initialDirectory: initialDirectory)
2575 .timeout (timeout);
76+ stopwatch.stop ();
77+ if (path == null ) {
78+ AppLogger .instance.log (
79+ '[DirectoryPicker] 用户取消选择: elapsed=${stopwatch .elapsedMilliseconds }ms' ,
80+ );
81+ } else {
82+ AppLogger .instance.log (
83+ '[DirectoryPicker] 原生选择器返回: '
84+ 'elapsed=${stopwatch .elapsedMilliseconds }ms, path=$path ' ,
85+ );
86+ }
2687 return path;
2788 } on TimeoutException {
28- // 原生对话框卡死的兜底,静默忽略
89+ stopwatch.stop ();
90+ // 注意:Dart Future 超时无法强制关闭已卡住的 Windows IFileDialog。
91+ AppLogger .instance.log (
92+ '[DirectoryPicker] Dart 等待超时: '
93+ 'elapsed=${stopwatch .elapsedMilliseconds }ms。'
94+ '原生 IFileDialog 可能仍在阻塞平台线程。' ,
95+ );
2996 return null ;
30- } catch (_) {
31- // 其他平台异常,静默忽略
97+ } catch (e, stackTrace) {
98+ stopwatch.stop ();
99+ AppLogger .instance.log (
100+ '[DirectoryPicker] 原生选择器异常: $e , '
101+ 'elapsed=${stopwatch .elapsedMilliseconds }ms' ,
102+ );
103+ AppLogger .instance.log ('[DirectoryPicker] StackTrace: $stackTrace ' );
32104 return null ;
33105 }
34106}
0 commit comments