diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 2e3a5061d83..614f76f5488 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -65,12 +65,6 @@ namespace Utils { static DeviceFileHooks s_deviceHooks; -/*! \class Utils::FileUtils - - \brief The FileUtils class contains file and directory related convenience - functions. - -*/ static bool removeRecursivelyLocal(const FilePath &filePath, QString *error) { @@ -78,9 +72,11 @@ static bool removeRecursivelyLocal(const FilePath &filePath, QString *error) QFileInfo fileInfo = filePath.toFileInfo(); if (!fileInfo.exists() && !fileInfo.isSymLink()) return true; - QFile::setPermissions(filePath.toString(), fileInfo.permissions() | QFile::WriteUser); + + QFile::setPermissions(fileInfo.absoluteFilePath(), fileInfo.permissions() | QFile::WriteUser); + if (fileInfo.isDir()) { - QDir dir(filePath.toString()); + QDir dir(fileInfo.absoluteFilePath()); dir.setPath(dir.canonicalPath()); if (dir.isRoot()) { if (error) { @@ -122,85 +118,9 @@ static bool removeRecursivelyLocal(const FilePath &filePath, QString *error) return true; } -// Cleans part after optional :// scheme separator, similar to QDir::cleanPath() -// - directory separators normalized (that is, platform-native -// separators converted to "/") and redundant ones removed, and "."s and ".."s -// resolved (as far as possible). -// Symbolic links are kept. This function does not return the -// canonical path, but rather the simplest version of the input. -// For example, "./local" becomes "local", "local/../bin" becomes -// "bin" and "/local/usr/../bin" becomes "/local/bin". - -// FIXME: This should not use the host-platform dependent QDir::cleanPath() - -static QString doCleanPath(const QString &input) +void FilePath::setDeviceFileHooks(const DeviceFileHooks &hooks) { - const int pos = input.indexOf("://"); - if (pos == -1) - return QDir::cleanPath(input); - return input.left(pos + 3) + QDir::cleanPath(input.mid(pos + 3)); -} - -/*! - 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) -{ - 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; - } - return true; - }); -} - -/*! - Copies a file specified by \a srcFilePath to \a tgtFilePath only if \a srcFilePath is different - (file contents and last modification time). - - Returns whether the operation succeeded. -*/ - -bool FileUtils::copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath) -{ - QTC_ASSERT(srcFilePath.exists(), return false); - QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false); - QTC_ASSERT(srcFilePath.host() == tgtFilePath.host(), return false); - - if (tgtFilePath.exists()) { - const QDateTime srcModified = srcFilePath.lastModified(); - const QDateTime tgtModified = tgtFilePath.lastModified(); - if (srcModified == tgtModified) { - const QByteArray srcContents = srcFilePath.fileContents(); - const QByteArray tgtContents = srcFilePath.fileContents(); - if (srcContents == tgtContents) - return true; - } - tgtFilePath.removeFile(); - } - - return srcFilePath.copyFile(tgtFilePath); + s_deviceHooks = hooks; } /*! @@ -314,85 +234,6 @@ QString FilePath::shortNativePath() const return toUserOutput(); } -QString FileUtils::fileSystemFriendlyName(const QString &name) -{ - QString result = name; - result.replace(QRegularExpression(QLatin1String("\\W")), QLatin1String("_")); - result.replace(QRegularExpression(QLatin1String("_+")), QLatin1String("_")); // compact _ - result.remove(QRegularExpression(QLatin1String("^_*"))); // remove leading _ - result.remove(QRegularExpression(QLatin1String("_+$"))); // remove trailing _ - if (result.isEmpty()) - result = QLatin1String("unknown"); - return result; -} - -int FileUtils::indexOfQmakeUnfriendly(const QString &name, int startpos) -{ - static const QRegularExpression checkRegExp(QLatin1String("[^a-zA-Z0-9_.-]")); - return checkRegExp.match(name, startpos).capturedStart(); -} - -QString FileUtils::qmakeFriendlyName(const QString &name) -{ - QString result = name; - - // Remove characters that might trip up a build system (especially qmake): - int pos = indexOfQmakeUnfriendly(result); - while (pos >= 0) { - result[pos] = QLatin1Char('_'); - pos = indexOfQmakeUnfriendly(result, pos); - } - return fileSystemFriendlyName(result); -} - -bool FileUtils::makeWritable(const FilePath &path) -{ - return path.setPermissions(path.permissions() | QFile::WriteUser); -} - -// makes sure that capitalization of directories is canonical on Windows and OS X. -// This mimics the logic in QDeclarative_isFileCaseCorrect -QString FileUtils::normalizedPathName(const QString &name) -{ -#ifdef Q_OS_WIN - const QString nativeSeparatorName(QDir::toNativeSeparators(name)); - const auto nameC = reinterpret_cast(nativeSeparatorName.utf16()); // MinGW - PIDLIST_ABSOLUTE file; - HRESULT hr = SHParseDisplayName(nameC, NULL, &file, 0, NULL); - if (FAILED(hr)) - return name; - TCHAR buffer[MAX_PATH]; - const bool success = SHGetPathFromIDList(file, buffer); - ILFree(file); - return success ? QDir::fromNativeSeparators(QString::fromUtf16(reinterpret_cast(buffer))) - : name; -#elif defined(Q_OS_OSX) - return Internal::normalizePathName(name); -#else // do not try to handle case-insensitive file systems on Linux - return name; -#endif -} - -static bool isRelativePathHelper(const QString &path, OsType osType) -{ - if (path.startsWith('/')) - return false; - if (osType == OsType::OsTypeWindows) { - if (path.startsWith('\\')) - return false; - // Unlike QFileInfo, this won't accept a relative path with a drive letter. - // Such paths result in a royal mess anyway ... - if (path.length() >= 3 && path.at(1) == ':' && path.at(0).isLetter() - && (path.at(2) == '/' || path.at(2) == '\\')) - return false; - } - return true; -} - -bool FileUtils::isRelativePath(const QString &path) -{ - return isRelativePathHelper(path, HostOsInfo::hostOs()); -} bool FilePath::isRelativePath() const { @@ -422,20 +263,6 @@ FilePath FilePath::cleanPath() const return result; } -FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &filePath) -{ - FilePath newCommonPath = oldCommonPath; - while (!newCommonPath.isEmpty() && !filePath.isChildOf(newCommonPath)) - newCommonPath = newCommonPath.parentDir(); - return newCommonPath.canonicalPath(); -} - -FilePath FileUtils::homePath() -{ - return FilePath::fromString(doCleanPath(QDir::homePath())); -} - - /*! \class Utils::FilePath \brief The FilePath class is an abstraction for handles to objects @@ -597,11 +424,6 @@ QUrl FilePath::toUrl() const return url; } -void FileUtils::setDeviceFileHooks(const DeviceFileHooks &hooks) -{ - s_deviceHooks = hooks; -} - /// \returns a QString to display to the user, including the device prefix /// Converts the separators to the native format of the system /// this path belongs to. @@ -1175,13 +997,11 @@ void FilePath::setFromString(const QString &filename) } m_data = "/"; - - } else { - m_scheme.clear(); - m_host.clear(); - m_data = filename; + return; } - + m_scheme.clear(); + m_host.clear(); + m_data = filename; return; } } diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index 704fd217df0..e478db7c5d9 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -47,6 +47,7 @@ class tst_fileutils; // This becomes a friend of Utils::FilePath for testing pri namespace Utils { +class DeviceFileHooks; class Environment; class EnvironmentChange; @@ -222,6 +223,8 @@ public: [[nodiscard]] static QString specialPath(SpecialPathComponent component); [[nodiscard]] static FilePath specialFilePath(SpecialPathComponent component); + static void setDeviceFileHooks(const DeviceFileHooks &hooks); + private: friend class ::tst_fileutils; static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath); @@ -238,6 +241,47 @@ inline size_t qHash(const Utils::FilePath &a, uint seed = 0) return a.hash(seed); } +class DeviceFileHooks +{ +public: + std::function isExecutableFile; + std::function isReadableFile; + std::function isReadableDir; + std::function isWritableDir; + std::function isWritableFile; + std::function isFile; + std::function isDir; + std::function ensureWritableDir; + std::function ensureExistingFile; + std::function createDir; + std::function exists; + std::function removeFile; + std::function removeRecursively; + std::function copyFile; + std::function renameFile; + std::function searchInPath; + std::function symLinkTarget; + std::function mapToDevicePath; + std::function &, // Abort on 'false' return. + const FileFilter &)> iterateDirectory; + std::function fileContents; + std::function writeFileContents; + std::function lastModified; + std::function permissions; + std::function setPermissions; + std::function osType; + std::function environment; + std::function fileSize; + std::function bytesAvailable; + std::function deviceDisplayName; + + template using Continuation = std::function; + std::function &, const FilePath &, const FilePath &)> asyncCopyFile; + std::function &, const FilePath &, qint64, qint64)> asyncFileContents; + std::function &, const FilePath &, const QByteArray &)> asyncWriteFileContents; +}; + } // namespace Utils QT_BEGIN_NAMESPACE @@ -254,4 +298,5 @@ struct QTCREATOR_UTILS_EXPORT hash using result_type = size_t; result_type operator()(const argument_type &fn) const; }; + } // namespace std diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index bc558fc5a84..0cf29bb2bda 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -28,6 +28,7 @@ #include "algorithm.h" #include "qtcassert.h" +#include "hostosinfo.h" #include "fsengine/fileiconprovider.h" #include "fsengine/fsengine.h" @@ -35,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -285,6 +287,13 @@ TempFileSaver::~TempFileSaver() QFile::remove(m_filePath.toString()); } +/*! \class Utils::FileUtils + + \brief The FileUtils class contains file and directory related convenience + functions. + +*/ + #ifdef QT_GUI_LIB FileUtils::CopyAskingForOverwrite::CopyAskingForOverwrite(QWidget *dialogParent, const std::function &postOperation) : m_parent(dialogParent) @@ -629,4 +638,178 @@ void FileUtils::iterateLsOutput(const FilePath &base, #endif // QT_WIDGETS_LIB +/*! + 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) +{ + 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; + } + return true; + }); +} + +/*! + Copies a file specified by \a srcFilePath to \a tgtFilePath only if \a srcFilePath is different + (file contents and last modification time). + + Returns whether the operation succeeded. +*/ + +bool FileUtils::copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath) +{ + QTC_ASSERT(srcFilePath.exists(), return false); + QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false); + QTC_ASSERT(srcFilePath.host() == tgtFilePath.host(), return false); + + if (tgtFilePath.exists()) { + const QDateTime srcModified = srcFilePath.lastModified(); + const QDateTime tgtModified = tgtFilePath.lastModified(); + if (srcModified == tgtModified) { + const QByteArray srcContents = srcFilePath.fileContents(); + const QByteArray tgtContents = srcFilePath.fileContents(); + if (srcContents == tgtContents) + return true; + } + tgtFilePath.removeFile(); + } + + return srcFilePath.copyFile(tgtFilePath); +} + +QString FileUtils::fileSystemFriendlyName(const QString &name) +{ + QString result = name; + result.replace(QRegularExpression(QLatin1String("\\W")), QLatin1String("_")); + result.replace(QRegularExpression(QLatin1String("_+")), QLatin1String("_")); // compact _ + result.remove(QRegularExpression(QLatin1String("^_*"))); // remove leading _ + result.remove(QRegularExpression(QLatin1String("_+$"))); // remove trailing _ + if (result.isEmpty()) + result = QLatin1String("unknown"); + return result; +} + +int FileUtils::indexOfQmakeUnfriendly(const QString &name, int startpos) +{ + static const QRegularExpression checkRegExp(QLatin1String("[^a-zA-Z0-9_.-]")); + return checkRegExp.match(name, startpos).capturedStart(); +} + +QString FileUtils::qmakeFriendlyName(const QString &name) +{ + QString result = name; + + // Remove characters that might trip up a build system (especially qmake): + int pos = indexOfQmakeUnfriendly(result); + while (pos >= 0) { + result[pos] = QLatin1Char('_'); + pos = indexOfQmakeUnfriendly(result, pos); + } + return fileSystemFriendlyName(result); +} + +bool FileUtils::makeWritable(const FilePath &path) +{ + return path.setPermissions(path.permissions() | QFile::WriteUser); +} + +// makes sure that capitalization of directories is canonical on Windows and macOS. +// This mimics the logic in QDeclarative_isFileCaseCorrect +QString FileUtils::normalizedPathName(const QString &name) +{ +#ifdef Q_OS_WIN + const QString nativeSeparatorName(QDir::toNativeSeparators(name)); + const auto nameC = reinterpret_cast(nativeSeparatorName.utf16()); // MinGW + PIDLIST_ABSOLUTE file; + HRESULT hr = SHParseDisplayName(nameC, NULL, &file, 0, NULL); + if (FAILED(hr)) + return name; + TCHAR buffer[MAX_PATH]; + const bool success = SHGetPathFromIDList(file, buffer); + ILFree(file); + return success ? QDir::fromNativeSeparators(QString::fromUtf16(reinterpret_cast(buffer))) + : name; +#elif defined(Q_OS_MACOS) + return Internal::normalizePathName(name); +#else // do not try to handle case-insensitive file systems on Linux + return name; +#endif +} + +bool isRelativePathHelper(const QString &path, OsType osType) +{ + if (path.startsWith('/')) + return false; + if (osType == OsType::OsTypeWindows) { + if (path.startsWith('\\')) + return false; + // Unlike QFileInfo, this won't accept a relative path with a drive letter. + // Such paths result in a royal mess anyway ... + if (path.length() >= 3 && path.at(1) == ':' && path.at(0).isLetter() + && (path.at(2) == '/' || path.at(2) == '\\')) + return false; + } + return true; +} + +bool FileUtils::isRelativePath(const QString &path) +{ + return isRelativePathHelper(path, HostOsInfo::hostOs()); +} + +// Cleans part after optional :// scheme separator, similar to QDir::cleanPath() +// - directory separators normalized (that is, platform-native +// separators converted to "/") and redundant ones removed, and "."s and ".."s +// resolved (as far as possible). +// Symbolic links are kept. This function does not return the +// canonical path, but rather the simplest version of the input. +// For example, "./local" becomes "local", "local/../bin" becomes +// "bin" and "/local/usr/../bin" becomes "/local/bin". + +// FIXME: This should not use the host-platform dependent QDir::cleanPath() + +QString doCleanPath(const QString &input) +{ + const int pos = input.indexOf("://"); + if (pos == -1) + return QDir::cleanPath(input); + return input.left(pos + 3) + QDir::cleanPath(input.mid(pos + 3)); +} + +FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &filePath) +{ + FilePath newCommonPath = oldCommonPath; + while (!newCommonPath.isEmpty() && !filePath.isChildOf(newCommonPath)) + newCommonPath = newCommonPath.parentDir(); + return newCommonPath.canonicalPath(); +} + +FilePath FileUtils::homePath() +{ + return FilePath::fromString(doCleanPath(QDir::homePath())); +} + } // namespace Utils diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 934805f52df..a1c691b88df 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -53,47 +53,6 @@ QT_END_NAMESPACE namespace Utils { -class DeviceFileHooks -{ -public: - std::function isExecutableFile; - std::function isReadableFile; - std::function isReadableDir; - std::function isWritableDir; - std::function isWritableFile; - std::function isFile; - std::function isDir; - std::function ensureWritableDir; - std::function ensureExistingFile; - std::function createDir; - std::function exists; - std::function removeFile; - std::function removeRecursively; - std::function copyFile; - std::function renameFile; - std::function searchInPath; - std::function symLinkTarget; - std::function mapToDevicePath; - std::function &, // Abort on 'false' return. - const FileFilter &)> iterateDirectory; - std::function fileContents; - std::function writeFileContents; - std::function lastModified; - std::function permissions; - std::function setPermissions; - std::function osType; - std::function environment; - std::function fileSize; - std::function bytesAvailable; - std::function deviceDisplayName; - - template using Continuation = std::function; - std::function &, const FilePath &, const FilePath &)> asyncCopyFile; - std::function &, const FilePath &, qint64, qint64)> asyncFileContents; - std::function &, const FilePath &, const QByteArray &)> asyncWriteFileContents; -}; - class QTCREATOR_UTILS_EXPORT FileUtils { public: @@ -138,8 +97,6 @@ public: static QByteArray fileId(const FilePath &fileName); static FilePath homePath(); - static void setDeviceFileHooks(const DeviceFileHooks &hooks); - static void iterateLsOutput(const FilePath &base, const QStringList &entries, const FileFilter &filter, @@ -328,5 +285,8 @@ private: QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &fn); +bool isRelativePathHelper(const QString &path, OsType osType); +QString doCleanPath(const QString &input); + } // namespace Utils diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 65d7ce56056..4683230af69 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -610,7 +610,7 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniquedisplayName(); }; - FileUtils::setDeviceFileHooks(deviceHooks); + FilePath::setDeviceFileHooks(deviceHooks); DeviceProcessHooks processHooks; diff --git a/tests/auto/utils/fsengine/tst_fsengine.cpp b/tests/auto/utils/fsengine/tst_fsengine.cpp index 574a2bac61a..75c636b6bac 100644 --- a/tests/auto/utils/fsengine/tst_fsengine.cpp +++ b/tests/auto/utils/fsengine/tst_fsengine.cpp @@ -175,7 +175,7 @@ void tst_fsengine::initTestCase() deviceHooks.mapToDevicePath = [](const FilePath &filePath) { return filePath.path(); }; - FileUtils::setDeviceFileHooks(deviceHooks); + FilePath::setDeviceFileHooks(deviceHooks); FSEngine::addDevice(FilePath::fromString("device://test"));