From b5c93e7f00c5ac1902a85a57aadc4c22d7172091 Mon Sep 17 00:00:00 2001 From: DebuggerX Date: Wed, 15 Jul 2026 00:46:34 +0800 Subject: [PATCH] Fix OS Error: Cross-device link, errno = 18 --- client/lib/utils.dart | 65 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/client/lib/utils.dart b/client/lib/utils.dart index c5dc7ee..a007b10 100644 --- a/client/lib/utils.dart +++ b/client/lib/utils.dart @@ -181,18 +181,19 @@ Future download(BuildContext context, SyncItemConfig item, dav.File cloudA if (directory.existsSync()) { directory.deleteSync(recursive: true); } - Directory(temp.path); - Directory(temp.path).renameSync(item.path); + await Directory(temp.path).safeMoveDirectory(item.path); case FileSystemEntityType.file: var file = File(item.path); if (file.existsSync()) { file.deleteSync(); } - temp.renameSync(item.path); + await temp.safeMoveFile(item.path); default: print('Failed: $fileSystemEntityType'); } + loadLocalStatus(); + if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -211,3 +212,61 @@ extension DateTimeExt on DateTime { String padZero(int num, [fix = 2]) => num.toString().padLeft(fix, '0'); } + +/// 安全地移动文件和目录(支持跨分区) + +extension on File { + Future safeMoveFile(String destPath) async { + try { + await rename(destPath); + return; + } on FileSystemException catch (e) { + // 捕获到 errno = 18 (EXDEV) 跨分区错误时,退化到“复制+删除”方案 + if (e.osError?.errorCode != 18) { + rethrow; // 其他错误继续抛出 + } + } + + await copy(destPath); + + await delete(); + } +} + +extension on Directory { + Future safeMoveDirectory(String destPath) async { + // 1. 如果在同一分区,优先尝试 rename,效率最高 + try { + await rename(destPath); + return; + } on FileSystemException catch (e) { + // 捕获到 errno = 18 (EXDEV) 跨分区错误时,退化到“复制+删除”方案 + if (e.osError?.errorCode != 18) { + rethrow; // 其他错误继续抛出 + } + } + + // 2. 跨分区移动:递归复制 + await _copyDirectory(this, Directory(destPath)); + + // 3. 复制完成后删除源目录 + await delete(recursive: true); + } +} + +/// 递归复制目录 +Future _copyDirectory(Directory source, Directory destination) async { + // 确保目标目录存在 + await destination.create(recursive: true); + + await for (var entity in source.list(recursive: false)) { + // 计算在新目录下的对应路径 + final newPath = '${destination.path}/${entity.uri.pathSegments.lastWhere((s) => s.isNotEmpty)}'; + + if (entity is File) { + await entity.copy(newPath); + } else if (entity is Directory) { + await _copyDirectory(entity, Directory(newPath)); + } + } +}