From 740cffb8f119d0ad50a369f9e53c489c522461d6 Mon Sep 17 00:00:00 2001 From: liujinchang Date: Mon, 7 Sep 2026 17:14:56 +0800 Subject: [PATCH] fix(data-transfer): fix moveFile failure when source and target are on different filesystems MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move directories with QDir::rename instead of QFile::rename, which fails with EISDIR on directories - Fall back to recursive copy plus source removal when rename fails with EXDEV across filesystems: copyDirectory() for directories and QFile::copy for single files - Fix rename-collision handling: dst.remove(fileName) deleted every occurrence of the name and corrupted paths with repeated segments; only the trailing segment is cut now - Ensure the destination parent directory exists before moving, for targets such as ~/D:/ derived from Windows drive letters 修复(数据迁移): 修复源和目标位于不同文件系统时 moveFile 搬运失败的问题 - 目录改用 QDir::rename 移动,避免 QFile::rename 移动目录时报 EISDIR 错误 - rename 跨文件系统失败(EXDEV)时退化为复制后删除源:目录走 copyDirectory() 递归复制,单文件走 QFile::copy - 修复重名避让逻辑: dst.remove(fileName) 会误删路径中所有同名段导致路径破坏,改为只截取结尾段 - 移动前确保目标父目录存在,兼容 ~/D:/ 这类源自 Windows 盘符的目标路径 Log: 修复数据迁移搬运阶段接收目录与主目录不在同一文件系统时 rename 跨设备失败(EXDEV)导致搬运失败的问题 Bug: https://pms.uniontech.com/bug-view-375881.html --- .../core/utils/settinghepler.cpp | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/plugins/data-transfer/core/utils/settinghepler.cpp b/src/plugins/data-transfer/core/utils/settinghepler.cpp index 0964ad7d7..40a2fc003 100644 --- a/src/plugins/data-transfer/core/utils/settinghepler.cpp +++ b/src/plugins/data-transfer/core/utils/settinghepler.cpp @@ -294,12 +294,37 @@ bool SettingHelper::setFile(QJsonObject jsonObj, QString filepath) return true; } +// 递归复制目录, 用于跨设备(EXDEV) rename 失败时的退化搬运 +static bool copyDirectory(const QString &src, const QString &dst) +{ + QDir srcDir(src); + if (!srcDir.exists()) + return false; + if (!QDir().mkpath(dst)) + return false; + + const auto entries = srcDir.entryInfoList( + QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + for (const auto &entry : entries) { + const QString target = dst + QLatin1Char('/') + entry.fileName(); + if (entry.isDir()) { + if (!copyDirectory(entry.absoluteFilePath(), target)) + return false; + } else if (!QFile::copy(entry.absoluteFilePath(), target)) { + WLOG << "copy file failed: " << entry.absoluteFilePath().toStdString() + << " -> " << target.toStdString(); + return false; + } + } + return true; +} + bool SettingHelper::moveFile(const QString &src, QString &dst) { if (QFile::exists(dst)) { int i = 1; - QString fileName = dst.split("/").last(); - QString dstDir = dst.remove(fileName); + QString fileName = dst.section('/', -1); // 只取结尾的文件名 + QString dstDir = dst.left(dst.size() - fileName.size()); // 只截掉结尾段,避免误删路径中间的同名段 QStringList filenamelist = fileName.split("."); QString suffix; if (!QFileInfo(src).isDir() && filenamelist.size() >= 2) { @@ -307,17 +332,41 @@ bool SettingHelper::moveFile(const QString &src, QString &dst) suffix = "." + suffix; } QString baseName = fileName; - baseName = fileName.remove(suffix); + if (baseName.endsWith(suffix)) // 只去掉结尾的后缀 + baseName.chop(suffix.size()); while (QFile::exists(dst)) { dst = dstDir + "/" + baseName + "(" + QString::number(i) + ")" + suffix; i++; } } + // 兜底确保目标父目录存在(如 ~/D: 这类盘符目录) + QDir().mkpath(QFileInfo(dst).absolutePath()); + + // ---------- 目录处理 ---------- + // 优先 QDir::rename 移动; 跨设备(EXDEV) rename 失败时, 退化为递归复制整棵目录树后删除源 + if (QFileInfo(src).isDir()) { + QDir dir; + if (dir.rename(src, dst)) + return true; + if (copyDirectory(src, dst)) { + QDir(src).removeRecursively(); // 数据已就位, 源清理失败不影响结果 + return true; + } + + WLOG << "moveFile error: rename dir failed: " << src.toStdString() << " -> " << dst.toStdString(); + return false; + } + // ---------- 文件处理 ---------- + // 优先 rename 移动; 跨设备时退化为复制后删除源 QFile f(src); LOG << "moveFile dst: " << src.toStdString() << " " << dst.toStdString(); if (f.rename(dst)) return true; + if (QFile::copy(src, dst)) { // 跨设备时退化为复制后删除源 + QFile::remove(src); + return true; + } WLOG << "moveFile error: " << f.errorString().toStdString(); return false;