From 7cb74e325f3adf54972d5257ab328ebeccbaf523 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 20 Jan 2023 11:30:21 +0100 Subject: [PATCH] Utils: Add FilePath::copyRecursively Change-Id: I0cb07158906a5e163ea35670f46f3b4fd9ec40b8 Reviewed-by: Reviewed-by: hjk Reviewed-by: Qt CI Bot --- src/libs/utils/devicefileaccess.cpp | 99 +++++++++++++++++++ src/libs/utils/devicefileaccess.h | 2 + src/libs/utils/filepath.cpp | 5 + src/libs/utils/filepath.h | 1 + src/libs/utils/fileutils.cpp | 57 +++++------ src/libs/utils/fileutils.h | 47 ++------- src/plugins/projectexplorer/project.cpp | 7 +- .../qtsupport/gettingstartedwelcomepage.cpp | 20 ++-- .../studiowelcome/studiowelcomeplugin.cpp | 7 +- 9 files changed, 158 insertions(+), 87 deletions(-) diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 7709e6fa23e..fff06a0aec8 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -11,6 +11,10 @@ #include "qtcassert.h" #include "utilstr.h" +#ifndef UTILS_STATIC_LIBRARY +#include "qtcprocess.h" +#endif + #include #include #include @@ -147,6 +151,101 @@ expected_str DeviceFileAccess::copyFile(const FilePath &filePath, const Fi Tr::tr("copyFile is not implemented for \"%1\"").arg(filePath.toUserOutput())); } +expected_str copyRecursively_fallback(const FilePath &src, const FilePath &target) +{ + QString error; + src.iterateDirectory( + [&target, &src, &error](const FilePath &path) { + const FilePath relative = path.relativePathFrom(src); + const FilePath targetPath = target.pathAppended(relative.path()); + + if (!targetPath.parentDir().ensureWritableDir()) { + error = QString("Could not create directory %1") + .arg(targetPath.parentDir().toUserOutput()); + return false; + } + + const expected_str result = path.copyFile(targetPath); + if (!result) { + error = result.error(); + return false; + } + return true; + }, + {{"*"}, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::Subdirectories}); + + if (error.isEmpty()) + return {}; + + return make_unexpected(error); +} + +expected_str DeviceFileAccess::copyRecursively(const FilePath &src, + const FilePath &target) const +{ +#ifdef UTILS_STATIC_LIBRARY + return copyRecursively_fallback(src, target); +#else + if (!target.isWritableDir()) { + return make_unexpected(Tr::tr("Cannot copy %1 to %2, it is not a writable directory.") + .arg(src.toUserOutput()) + .arg(target.toUserOutput())); + } + + const FilePath tar = FilePath::fromString("tar").onDevice(target); + const FilePath targetTar = tar.searchInPath(); + + const FilePath sTar = FilePath::fromString("tar").onDevice(src); + const FilePath sourceTar = sTar.searchInPath(); + + if (!targetTar.isExecutableFile() || !sourceTar.isExecutableFile()) + return copyRecursively_fallback(src, target); + + QtcProcess srcProcess; + QtcProcess targetProcess; + + targetProcess.setProcessMode(ProcessMode::Writer); + + QObject::connect(&srcProcess, + &QtcProcess::readyReadStandardOutput, + &targetProcess, + [&srcProcess, &targetProcess]() { + targetProcess.writeRaw(srcProcess.readAllRawStandardOutput()); + }); + + srcProcess.setCommand({sourceTar, {"-C", src.path(), "-cf", "-", "."}}); + targetProcess.setCommand({targetTar, {"xf", "-", "-C", target.path()}}); + + targetProcess.start(); + targetProcess.waitForStarted(); + + srcProcess.start(); + srcProcess.waitForFinished(); + + targetProcess.closeWriteChannel(); + + if (srcProcess.result() != ProcessResult::FinishedWithSuccess) { + targetProcess.kill(); + return make_unexpected( + Tr::tr("Failed to copy recursively from \"%1\" to \"%2\" while " + "trying to create tar archive from source: %3") + .arg(src.toUserOutput(), target.toUserOutput(), srcProcess.readAllStandardError())); + } + + targetProcess.waitForFinished(); + + if (targetProcess.result() != ProcessResult::FinishedWithSuccess) { + return make_unexpected(Tr::tr("Failed to copy recursively from \"%1\" to \"%2\" while " + "trying to extract tar archive to target: %3") + .arg(src.toUserOutput(), + target.toUserOutput(), + targetProcess.readAllStandardError())); + } + + return {}; +#endif +} + bool DeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &target) const { Q_UNUSED(filePath) diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h index e25507e28b0..4b1a917ab10 100644 --- a/src/libs/utils/devicefileaccess.h +++ b/src/libs/utils/devicefileaccess.h @@ -37,6 +37,8 @@ protected: virtual bool removeFile(const FilePath &filePath) const; virtual bool removeRecursively(const FilePath &filePath, QString *error) const; virtual expected_str copyFile(const FilePath &filePath, const FilePath &target) const; + virtual expected_str copyRecursively(const FilePath &filePath, + const FilePath &target) const; virtual bool renameFile(const FilePath &filePath, const FilePath &target) const; virtual OsType osType(const FilePath &filePath) const; diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index eae06927a47..ea14cae1e74 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -1398,6 +1398,11 @@ bool FilePath::removeRecursively(QString *error) const return fileAccess()->removeRecursively(*this, error); } +expected_str FilePath::copyRecursively(const FilePath &target) const +{ + return fileAccess()->copyRecursively(*this, target); +} + expected_str FilePath::copyFile(const FilePath &target) const { if (host() != target.host()) { diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 658aaec8e0a..36747f8994a 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -122,6 +122,7 @@ public: OsType osType() const; bool removeFile() const; bool removeRecursively(QString *error = nullptr) const; + expected_str copyRecursively(const FilePath &target) const; expected_str copyFile(const FilePath &target) const; bool renameFile(const FilePath &target) const; qint64 fileSize() const; diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index e44102f4c7d..44bf0bab202 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -637,38 +637,35 @@ FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos) return {size, flags, dt}; } -/*! - Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain - the target directory, which will be created. Example usage: - - \code - QString error; - bool ok = Utils::FileUtils::copyRecursively("/foo/bar", "/foo/baz", &error); - if (!ok) - qDebug() << error; - \endcode - - This will copy the contents of /foo/bar into to the baz directory under /foo, which will be created in the process. - - \note The \a error parameter is optional. - - Returns whether the operation succeeded. -*/ - -bool FileUtils::copyRecursively(const FilePath &srcFilePath, const FilePath &tgtFilePath, QString *error) +bool FileUtils::copyRecursively( + const FilePath &srcFilePath, + const FilePath &tgtFilePath, + QString *error, + std::function copyHelper) { - return copyRecursively( - srcFilePath, tgtFilePath, error, [](const FilePath &src, const FilePath &dest, QString *error) { - if (!src.copyFile(dest)) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Could not copy file \"%1\" to \"%2\".") - .arg(src.toUserOutput(), dest.toUserOutput()); - } - return false; + if (srcFilePath.isDir()) { + if (!tgtFilePath.ensureWritableDir()) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Failed to create directory \"%1\".") + .arg(tgtFilePath.toUserOutput()); } - return true; - }); + return false; + } + const QDir sourceDir(srcFilePath.toString()); + const QStringList fileNames = sourceDir.entryList( + QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + for (const QString &fileName : fileNames) { + const FilePath newSrcFilePath = srcFilePath / fileName; + const FilePath newTgtFilePath = tgtFilePath / fileName; + if (!copyRecursively(newSrcFilePath, newTgtFilePath, error, copyHelper)) + return false; + } + } else { + if (!copyHelper(srcFilePath, tgtFilePath, error)) + return false; + } + return true; } /*! diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 80f9bc4100b..80a89c6d52a 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -61,14 +61,12 @@ public: }; #endif // QT_GUI_LIB - static bool copyRecursively(const FilePath &srcFilePath, - const FilePath &tgtFilePath, - QString *error = nullptr); - template - static bool copyRecursively(const FilePath &srcFilePath, - const FilePath &tgtFilePath, - QString *error, - T &©Helper); + static bool copyRecursively( + const FilePath &srcFilePath, + const FilePath &tgtFilePath, + QString *error, + std::function helper); + static bool copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath); static QString fileSystemFriendlyName(const QString &name); @@ -126,39 +124,6 @@ public: }; -template -bool FileUtils::copyRecursively(const FilePath &srcFilePath, - const FilePath &tgtFilePath, - QString *error, - T &©Helper) -{ - if (srcFilePath.isDir()) { - if (!tgtFilePath.exists()) { - if (!tgtFilePath.ensureWritableDir()) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Failed to create directory \"%1\".") - .arg(tgtFilePath.toUserOutput()); - } - return false; - } - } - const QDir sourceDir(srcFilePath.toString()); - const QStringList fileNames = sourceDir.entryList( - QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); - for (const QString &fileName : fileNames) { - const FilePath newSrcFilePath = srcFilePath / fileName; - const FilePath newTgtFilePath = tgtFilePath / fileName; - if (!copyRecursively(newSrcFilePath, newTgtFilePath, error, copyHelper)) - return false; - } - } else { - if (!copyHelper(srcFilePath, tgtFilePath, error)) - return false; - } - return true; -} - // for actually finding out if e.g. directories are writable on Windows #ifdef Q_OS_WIN diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index 6bff1aa781b..fbd4f9e28fd 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -1421,11 +1421,10 @@ void ProjectExplorerPlugin::testProject_multipleBuildConfigs() // Copy project from qrc file and set it up. QTemporaryDir * const tempDir = TemporaryDirectory::masterTemporaryDirectory(); QVERIFY(tempDir->isValid()); - QString error; const FilePath projectDir = FilePath::fromString(tempDir->path() + "/generic-project"); - FileUtils::copyRecursively(":/projectexplorer/testdata/generic-project", - projectDir, &error); - QVERIFY2(error.isEmpty(), qPrintable(error)); + const auto copyResult = FilePath(":/projectexplorer/testdata/generic-project").copyRecursively(projectDir); + + QVERIFY2(copyResult, qPrintable(copyResult.error())); const QFileInfoList files = QDir(projectDir.toString()).entryInfoList(QDir::Files | QDir::Dirs); for (const QFileInfo &f : files) QFile(f.absoluteFilePath()).setPermissions(f.permissions() | QFile::WriteUser); diff --git a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp index b210ec3269c..36bbcfb902b 100644 --- a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp +++ b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp @@ -124,10 +124,12 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI QMessageBox::NoButton); return QString(); } else { - QString error; QString targetDir = destBaseDir + QLatin1Char('/') + exampleDirName; - if (FileUtils::copyRecursively(FilePath::fromString(projectDir), - FilePath::fromString(targetDir), &error)) { + + expected_str result + = FilePath::fromString(projectDir).copyRecursively(FilePath::fromString(targetDir)); + + if (result) { // set vars to new location const QStringList::Iterator end = filesToOpen.end(); for (QStringList::Iterator it = filesToOpen.begin(); it != end; ++it) @@ -136,21 +138,21 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI for (const QString &dependency : dependencies) { const FilePath targetFile = FilePath::fromString(targetDir) .pathAppended(QDir(dependency).dirName()); - if (!FileUtils::copyRecursively(FilePath::fromString(dependency), targetFile, - &error)) { + result = FilePath::fromString(dependency).copyRecursively(targetFile); + if (!result) { QMessageBox::warning(ICore::dialogParent(), Tr::tr("Cannot Copy Project"), - error); + result.error()); // do not fail, just warn; } } - return targetDir + QLatin1Char('/') + proFileInfo.fileName(); } else { - QMessageBox::warning(ICore::dialogParent(), Tr::tr("Cannot Copy Project"), error); + QMessageBox::warning(ICore::dialogParent(), + Tr::tr("Cannot Copy Project"), + result.error()); } - } } if (code == Keep) diff --git a/src/plugins/studiowelcome/studiowelcomeplugin.cpp b/src/plugins/studiowelcome/studiowelcomeplugin.cpp index ea570190317..17acc9b6510 100644 --- a/src/plugins/studiowelcome/studiowelcomeplugin.cpp +++ b/src/plugins/studiowelcome/studiowelcomeplugin.cpp @@ -629,9 +629,10 @@ WelcomeMode::WelcomeMode() m_dataModelDownloader = new DataModelDownloader(this); if (!m_dataModelDownloader->exists()) { //Fallback if data cannot be downloaded - Utils::FileUtils::copyRecursively(Utils::FilePath::fromUserInput(welcomePagePath - + "/dataImports"), - m_dataModelDownloader->targetFolder()); + // TODO: Check result? + Utils::FilePath::fromUserInput(welcomePagePath + "/dataImports") + .copyRecursively(m_dataModelDownloader->targetFolder()); + m_dataModelDownloader->setForceDownload(true); } Utils::FilePath readme = Utils::FilePath::fromUserInput(m_dataModelDownloader->targetFolder().toString()