diff options
author | hjk <hjk@qt.io> | 2021-07-16 11:16:45 +0200 |
---|---|---|
committer | hjk <hjk@qt.io> | 2021-07-22 11:52:58 +0000 |
commit | fd677101a9f7428ed6e86166e670397921b00989 (patch) | |
tree | 34edd24edc9fbdb6a631e5a59af4bebb23d0969d | |
parent | 805d19d1edf5d76f97d436161be79f2e39678bda (diff) | |
download | qt-creator-fd677101a9f7428ed6e86166e670397921b00989.tar.gz |
Utils: Split FilePath off fileutils.h
This now rather central class deserves a suitably named file(pair).
The rarer used declarations including otherwise not needed includes are
still in fileutils.h.
Change-Id: Ib18d72f16322c03f76023fdefcd5f6da35311b8b
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
-rw-r--r-- | src/libs/utils/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/libs/utils/filepath.cpp | 1329 | ||||
-rw-r--r-- | src/libs/utils/filepath.h | 198 | ||||
-rw-r--r-- | src/libs/utils/fileutils.cpp | 1409 | ||||
-rw-r--r-- | src/libs/utils/fileutils.h | 167 | ||||
-rw-r--r-- | src/libs/utils/utils-lib.pri | 2 | ||||
-rw-r--r-- | src/libs/utils/utils.qbs | 2 | ||||
-rw-r--r-- | src/plugins/projectexplorer/devicesupport/devicemanager.cpp | 2 | ||||
-rw-r--r-- | src/tools/sdktool/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/tools/sdktool/sdktool.pro | 2 | ||||
-rw-r--r-- | src/tools/sdktool/sdktool.qbs | 1 | ||||
-rw-r--r-- | tests/auto/debugger/gdb.pro | 2 |
12 files changed, 1617 insertions, 1499 deletions
diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index a8461a61f3..4f304d6ec2 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -52,6 +52,7 @@ add_qtc_library(Utils filecrumblabel.cpp filecrumblabel.h fileinprojectfinder.cpp fileinprojectfinder.h filenamevalidatinglineedit.cpp filenamevalidatinglineedit.h + filepath.cpp filepath.h filesearch.cpp filesearch.h filesystemwatcher.cpp filesystemwatcher.h fileutils.cpp fileutils.h diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp new file mode 100644 index 0000000000..eda575945a --- /dev/null +++ b/src/libs/utils/filepath.cpp @@ -0,0 +1,1329 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "filepath.h" + +#include "algorithm.h" +#include "commandline.h" +#include "environment.h" +#include "fileutils.h" +#include "hostosinfo.h" +#include "qtcassert.h" +#include "savefile.h" + +#include <QDataStream> +#include <QDateTime> +#include <QDebug> +#include <QFileInfo> +#include <QOperatingSystemVersion> +#include <QRegularExpression> +#include <QUrl> +#include <qplatformdefs.h> + +#ifdef Q_OS_WIN +#ifdef QTCREATOR_PCH_H +#define CALLBACK WINAPI +#endif +#include <qt_windows.h> +#include <shlobj.h> +#endif + +#ifdef Q_OS_OSX +#include "fileutils_mac.h" +#endif + +QT_BEGIN_NAMESPACE +QDebug operator<<(QDebug dbg, const Utils::FilePath &c) +{ + return dbg << c.toUserOutput(); +} + +QT_END_NAMESPACE + +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) +{ + QTC_ASSERT(!filePath.needsDevice(), return false); + QFileInfo fileInfo = filePath.toFileInfo(); + if (!fileInfo.exists() && !fileInfo.isSymLink()) + return true; + QFile::setPermissions(filePath.toString(), fileInfo.permissions() | QFile::WriteUser); + if (fileInfo.isDir()) { + QDir dir(filePath.toString()); + dir.setPath(dir.canonicalPath()); + if (dir.isRoot()) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Refusing to remove root directory."); + } + return false; + } + if (dir.path() == QDir::home().canonicalPath()) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Refusing to remove your home directory."); + } + return false; + } + + const QStringList fileNames = dir.entryList( + QDir::Files | QDir::Hidden | QDir::System | QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &fileName : fileNames) { + if (!removeRecursivelyLocal(filePath / fileName, error)) + return false; + } + if (!QDir::root().rmdir(dir.path())) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove directory \"%1\".") + .arg(filePath.toUserOutput()); + } + return false; + } + } else { + if (!QFile::remove(filePath.toString())) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove file \"%1\".") + .arg(filePath.toUserOutput()); + } + return false; + } + } + return true; +} + +/*! + 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; + book 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 QFileInfo &src, const QFileInfo &dest, QString *error) { + if (!QFile::copy(src.filePath(), dest.filePath())) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Could not copy file \"%1\" to \"%2\".") + .arg(FilePath::fromFileInfo(src).toUserOutput(), + FilePath::fromFileInfo(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); +} + +/*! + If this is a directory, the function will recursively check all files and return + true if one of them is newer than \a timeStamp. If this is a single file, true will + be returned if the file is newer than \a timeStamp. + + Returns whether at least one file in \a filePath has a newer date than + \a timeStamp. +*/ +bool FilePath::isNewerThan(const QDateTime &timeStamp) const +{ + if (!exists() || lastModified() >= timeStamp) + return true; + if (isDir()) { + const FilePaths dirContents = dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + for (const FilePath &entry : dirContents) { + if (entry.isNewerThan(timeStamp)) + return true; + } + } + return false; +} + +Qt::CaseSensitivity FilePath::caseSensitivity() const +{ + if (m_scheme.isEmpty()) + return HostOsInfo::fileNameCaseSensitivity(); + + // FIXME: This could or possibly should the target device's file name case sensitivity + // into account by diverting to IDevice. However, as this is expensive and we are + // in time-critical path here, we go with "good enough" for now: + // The first approximation is "Anything unusual is not case sensitive" + return Qt::CaseSensitive; +} + +/*! + Recursively resolves symlinks if this is a symlink. + To resolve symlinks anywhere in the path, see canonicalPath. + Unlike QFileInfo::canonicalFilePath(), this function will still return the expected deepest + target file even if the symlink is dangling. + + \note Maximum recursion depth == 16. + + Returns the symlink target file path. +*/ +FilePath FilePath::resolveSymlinks() const +{ + FilePath current = *this; + int links = 16; + while (links--) { + const FilePath target = current.symLinkTarget(); + if (target.isEmpty()) + return current; + current = target; + } + return current; +} + +/*! + Recursively resolves possibly present symlinks in this file name. + Unlike QFileInfo::canonicalFilePath(), this function will not return an empty + string if path doesn't exist. + + Returns the canonical path. +*/ +FilePath FilePath::canonicalPath() const +{ + if (needsDevice()) { + // FIXME: Not a full solution, but it stays on the right device. + return *this; + } + const QString result = toFileInfo().canonicalFilePath(); + if (result.isEmpty()) + return *this; + return FilePath::fromString(result); +} + +FilePath FilePath::operator/(const QString &str) const +{ + return pathAppended(str); +} + +void FilePath::clear() +{ + m_data.clear(); + m_host.clear(); + m_scheme.clear(); +} + +bool FilePath::isEmpty() const +{ + return m_data.isEmpty(); +} + +/*! + Like QDir::toNativeSeparators(), but use prefix '~' instead of $HOME on unix systems when an + absolute path is given. + + Returns the possibly shortened path with native separators. +*/ +QString FilePath::shortNativePath() const +{ + if (HostOsInfo::isAnyUnixHost()) { + const FilePath home = FileUtils::homePath(); + if (isChildOf(home)) { + return QLatin1Char('~') + QDir::separator() + + QDir::toNativeSeparators(relativeChildPath(home).toString()); + } + } + 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) +{ + const QString filePath = path.toString(); + return QFile::setPermissions(filePath, QFile::permissions(filePath) | QFile::WriteUser); +} + +// makes sure that capitalization of directories is canonical on Windows and OS X. +// This mimics the logic in QDeclarative_isFileCaseCorrect +QString FileUtils::normalizePathName(const QString &name) +{ +#ifdef Q_OS_WIN + const QString nativeSeparatorName(QDir::toNativeSeparators(name)); + const auto nameC = reinterpret_cast<LPCTSTR>(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<const ushort *>(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 +{ + return isRelativePathHelper(m_data, osType()); +} + +FilePath FilePath::resolvePath(const QString &fileName) const +{ + if (FileUtils::isAbsolutePath(fileName)) + return FilePath::fromString(QDir::cleanPath(fileName)); + FilePath result = *this; + result.setPath(QDir::cleanPath(m_data + '/' + fileName)); + return result; +} + +FilePath FilePath::cleanPath() const +{ + FilePath result = *this; + result.setPath(QDir::cleanPath(result.path())); + 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(QDir::cleanPath(QDir::homePath())); +} + +bool FileUtils::renameFile(const FilePath &srcFilePath, const FilePath &tgtFilePath) +{ + QTC_ASSERT(!srcFilePath.needsDevice(), return false); + QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false); + return QFile::rename(srcFilePath.path(), tgtFilePath.path()); +} + + +/*! \class Utils::FilePath + + \brief The FilePath class is a light-weight convenience class for filenames. + + On windows filenames are compared case insensitively. +*/ + +FilePath::FilePath() +{ +} + +/// Constructs a FilePath from \a info +FilePath FilePath::fromFileInfo(const QFileInfo &info) +{ + return FilePath::fromString(info.absoluteFilePath()); +} + +/// \returns a QFileInfo +QFileInfo FilePath::toFileInfo() const +{ + return QFileInfo(m_data); +} + +FilePath FilePath::fromUrl(const QUrl &url) +{ + FilePath fn; + fn.m_scheme = url.scheme(); + fn.m_host = url.host(); + fn.m_data = url.path(); + return fn; +} + +/// \returns a QString for passing on to QString based APIs +QString FilePath::toString() const +{ + if (m_scheme.isEmpty()) + return m_data; + if (m_data.startsWith('/')) + return m_scheme + "://" + m_host + m_data; + return m_scheme + "://" + m_host + "/./" + m_data; +} + +QUrl FilePath::toUrl() const +{ + QUrl url; + url.setScheme(m_scheme); + url.setHost(m_host); + url.setPath(m_data); + return url; +} + +void FileUtils::setDeviceFileHooks(const DeviceFileHooks &hooks) +{ + s_deviceHooks = hooks; +} + +/// \returns a QString to display to the user +/// Converts the separators to the native format +QString FilePath::toUserOutput() const +{ + if (m_scheme.isEmpty()) + return QDir::toNativeSeparators(m_data); + return toString(); +} + +QString FilePath::fileName() const +{ + const QChar slash = QLatin1Char('/'); + return m_data.mid(m_data.lastIndexOf(slash) + 1); +} + +QString FilePath::fileNameWithPathComponents(int pathComponents) const +{ + if (pathComponents < 0) + return m_data; + const QChar slash = QLatin1Char('/'); + int i = m_data.lastIndexOf(slash); + if (pathComponents == 0 || i == -1) + return m_data.mid(i + 1); + int component = i + 1; + // skip adjacent slashes + while (i > 0 && m_data.at(--i) == slash) + ; + while (i >= 0 && --pathComponents >= 0) { + i = m_data.lastIndexOf(slash, i); + component = i + 1; + while (i > 0 && m_data.at(--i) == slash) + ; + } + + // If there are no more slashes before the found one, return the entire string + if (i > 0 && m_data.lastIndexOf(slash, i) != -1) + return m_data.mid(component); + return m_data; +} + +/// \returns the base name of the file without the path. +/// +/// The base name consists of all characters in the file up to +/// (but not including) the first '.' character. + +QString FilePath::baseName() const +{ + const QString &name = fileName(); + return name.left(name.indexOf('.')); +} + +/// \returns the complete base name of the file without the path. +/// +/// The complete base name consists of all characters in the file up to +/// (but not including) the last '.' character + +QString FilePath::completeBaseName() const +{ + const QString &name = fileName(); + return name.left(name.lastIndexOf('.')); +} + +/// \returns the suffix (extension) of the file. +/// +/// The suffix consists of all characters in the file after +/// (but not including) the last '.'. + +QString FilePath::suffix() const +{ + const QString &name = fileName(); + const int index = name.lastIndexOf('.'); + if (index >= 0) + return name.mid(index + 1); + return {}; +} + +/// \returns the complete suffix (extension) of the file. +/// +/// The complete suffix consists of all characters in the file after +/// (but not including) the first '.'. + +QString FilePath::completeSuffix() const +{ + const QString &name = fileName(); + const int index = name.indexOf('.'); + if (index >= 0) + return name.mid(index + 1); + return {}; +} + +void FilePath::setScheme(const QString &scheme) +{ + QTC_CHECK(!scheme.contains('/')); + m_scheme = scheme; +} + +void FilePath::setHost(const QString &host) +{ + QTC_CHECK(!host.contains('/')); + m_host = host; +} + + +/// \returns a bool indicating whether a file with this +/// FilePath exists. +bool FilePath::exists() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.exists, return false); + return s_deviceHooks.exists(*this); + } + return !isEmpty() && QFileInfo::exists(m_data); +} + +/// \returns a bool indicating whether a path is writable. +bool FilePath::isWritableDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isWritableDir, return false); + return s_deviceHooks.isWritableDir(*this); + } + const QFileInfo fi{m_data}; + return exists() && fi.isDir() && fi.isWritable(); +} + +bool FilePath::isWritableFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isWritableFile, return false); + return s_deviceHooks.isWritableFile(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isWritable() && !fi.isDir(); +} + +bool FilePath::ensureWritableDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.ensureWritableDir, return false); + return s_deviceHooks.ensureWritableDir(*this); + } + const QFileInfo fi{m_data}; + if (exists() && fi.isDir() && fi.isWritable()) + return true; + return QDir().mkpath(m_data); +} + +bool FilePath::ensureExistingFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.ensureExistingFile, return false); + return s_deviceHooks.ensureExistingFile(*this); + } + QFile f(m_data); + if (f.exists()) + return true; + f.open(QFile::WriteOnly); + f.close(); + return f.exists(); +} + +bool FilePath::isExecutableFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isExecutableFile, return false); + return s_deviceHooks.isExecutableFile(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isExecutable() && !fi.isDir(); +} + +bool FilePath::isReadableFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isReadableFile, return false); + return s_deviceHooks.isReadableFile(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isReadable() && !fi.isDir(); +} + +bool FilePath::isReadableDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isReadableDir, return false); + return s_deviceHooks.isReadableDir(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isReadable() && fi.isDir(); +} + +bool FilePath::isFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isFile, return false); + return s_deviceHooks.isFile(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isFile(); +} + +bool FilePath::isDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.isDir, return false); + return s_deviceHooks.isDir(*this); + } + const QFileInfo fi{m_data}; + return fi.exists() && fi.isDir(); +} + +bool FilePath::createDir() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.createDir, return false); + return s_deviceHooks.createDir(*this); + } + QDir dir(m_data); + return dir.mkpath(dir.absolutePath()); +} + +FilePaths FilePath::dirEntries(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.dirEntries, return {}); + return s_deviceHooks.dirEntries(*this, nameFilters, filters, sort); + } + + const QFileInfoList entryInfoList = QDir(m_data).entryInfoList(nameFilters, filters, sort); + return Utils::transform(entryInfoList, &FilePath::fromFileInfo); +} + +FilePaths FilePath::filterEntriesHelper(const FilePath &base, + const QStringList &entries, + const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort) +{ + const QList<QRegularExpression> nameRegexps = transform(nameFilters, [](const QString &filter) { + QRegularExpression re; + re.setPattern(QRegularExpression::wildcardToRegularExpression(filter)); + QTC_CHECK(re.isValid()); + return re; + }); + + const auto nameMatches = [&nameRegexps](const QString &fileName) { + for (const QRegularExpression &re : nameRegexps) { + const QRegularExpressionMatch match = re.match(fileName); + if (match.hasMatch()) + return true; + } + return false; + }; + + // FIXME: Handle sort and filters. For now bark on unsupported options. + QTC_CHECK(filters == QDir::NoFilter); + QTC_CHECK(sort == QDir::NoSort); + + FilePaths result; + for (const QString &entry : entries) { + if (!nameMatches(entry)) + continue; + result.append(base.pathAppended(entry)); + } + return result; +} + +QList<FilePath> FilePath::dirEntries(QDir::Filters filters) const +{ + return dirEntries({}, filters); +} + +QByteArray FilePath::fileContents(qint64 maxSize, qint64 offset) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.fileContents, return {}); + return s_deviceHooks.fileContents(*this, maxSize, offset); + } + + const QString path = toString(); + QFile f(path); + if (!f.exists()) + return {}; + + if (!f.open(QFile::ReadOnly)) + return {}; + + if (offset != 0) + f.seek(offset); + + if (maxSize != -1) + return f.read(maxSize); + + return f.readAll(); +} + +bool FilePath::writeFileContents(const QByteArray &data) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.writeFileContents, return {}); + return s_deviceHooks.writeFileContents(*this, data); + } + + QFile file(path()); + QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return false); + qint64 res = file.write(data); + return res == data.size(); +} + +bool FilePath::needsDevice() const +{ + return !m_scheme.isEmpty(); +} + +/// \returns an empty FilePath if this is not a symbolic linl +FilePath FilePath::symLinkTarget() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.symLinkTarget, return {}); + return s_deviceHooks.symLinkTarget(*this); + } + const QFileInfo info(m_data); + if (!info.isSymLink()) + return {}; + return FilePath::fromString(info.symLinkTarget()); +} + +FilePath FilePath::withExecutableSuffix() const +{ + FilePath res = *this; + res.setPath(OsSpecificAspects::withExecutableSuffix(osType(), m_data)); + return res; +} + +/// Find the parent directory of a given directory. + +/// Returns an empty FilePath if the current directory is already +/// a root level directory. + +/// \returns \a FilePath with the last segment removed. +FilePath FilePath::parentDir() const +{ + const QString basePath = path(); + if (basePath.isEmpty()) + return FilePath(); + + const QDir base(basePath); + if (base.isRoot()) + return FilePath(); + + const QString path = basePath + QLatin1String("/.."); + const QString parent = QDir::cleanPath(path); + QTC_ASSERT(parent != path, return FilePath()); + + FilePath result = *this; + result.setPath(parent); + return result; +} + +FilePath FilePath::absolutePath() const +{ + FilePath result = *this; + result.m_data = QFileInfo(m_data).absolutePath(); + return result; +} + +FilePath FilePath::absoluteFilePath() const +{ + FilePath result = *this; + result.m_data = QFileInfo(m_data).absoluteFilePath(); + return result; +} + +FilePath FilePath::absoluteFilePath(const FilePath &tail) const +{ + if (isRelativePathHelper(tail.m_data, osType())) + return pathAppended(tail.m_data); + return tail; +} + +/// Constructs an absolute FilePath from this path which +/// is interpreted as being relative to \a anchor. +FilePath FilePath::absoluteFromRelativePath(const FilePath &anchor) const +{ + QDir anchorDir = QFileInfo(anchor.m_data).absoluteDir(); + QString absoluteFilePath = QFileInfo(anchorDir, m_data).canonicalFilePath(); + return FilePath::fromString(absoluteFilePath); +} + +/// Constructs a FilePath from \a filename +/// \a filename is not checked for validity. +FilePath FilePath::fromString(const QString &filename) +{ + FilePath fn; + if (filename.startsWith('/')) { + fn.m_data = filename; // fast track: absolute local paths + } else { + int pos1 = filename.indexOf("://"); + if (pos1 >= 0) { + fn.m_scheme = filename.left(pos1); + pos1 += 3; + int pos2 = filename.indexOf('/', pos1); + if (pos2 == -1) { + fn.m_data = filename.mid(pos1); + } else { + fn.m_host = filename.mid(pos1, pos2 - pos1); + fn.m_data = filename.mid(pos2); + } + if (fn.m_data.startsWith("/./")) + fn.m_data = fn.m_data.mid(3); + } else { + fn.m_data = filename; // treat everything else as local, too. + } + } + return fn; +} + +/// Constructs a FilePath from \a filePath. The \a defaultExtension is appended +/// to \a filename if that does not have an extension already. +/// \a filePath is not checked for validity. +FilePath FilePath::fromStringWithExtension(const QString &filepath, const QString &defaultExtension) +{ + if (filepath.isEmpty() || defaultExtension.isEmpty()) + return FilePath::fromString(filepath); + + QString rc = filepath; + QFileInfo fi(filepath); + // Add extension unless user specified something else + const QChar dot = QLatin1Char('.'); + if (!fi.fileName().contains(dot)) { + if (!defaultExtension.startsWith(dot)) + rc += dot; + rc += defaultExtension; + } + return FilePath::fromString(rc); +} + +/// Constructs a FilePath from \a filePath +/// \a filePath is only passed through QDir::fromNativeSeparators +FilePath FilePath::fromUserInput(const QString &filePath) +{ + QString clean = QDir::fromNativeSeparators(filePath); + if (clean.startsWith(QLatin1String("~/"))) + return FileUtils::homePath().pathAppended(clean.mid(2)); + return FilePath::fromString(clean); +} + +/// Constructs a FilePath from \a filePath, which is encoded as UTF-8. +/// \a filePath is not checked for validity. +FilePath FilePath::fromUtf8(const char *filename, int filenameSize) +{ + return FilePath::fromString(QString::fromUtf8(filename, filenameSize)); +} + +FilePath FilePath::fromVariant(const QVariant &variant) +{ + if (variant.type() == QVariant::Url) + return FilePath::fromUrl(variant.toUrl()); + return FilePath::fromString(variant.toString()); +} + +QVariant FilePath::toVariant() const +{ + return toString(); +} + +QDir FilePath::toDir() const +{ + return QDir(m_data); +} + +bool FilePath::operator==(const FilePath &other) const +{ + return QString::compare(m_data, other.m_data, caseSensitivity()) == 0 + && m_host == other.m_host + && m_scheme == other.m_scheme; +} + +bool FilePath::operator!=(const FilePath &other) const +{ + return !(*this == other); +} + +bool FilePath::operator<(const FilePath &other) const +{ + const int cmp = QString::compare(m_data, other.m_data, caseSensitivity()); + if (cmp != 0) + return cmp < 0; + if (m_host != other.m_host) + return m_host < other.m_host; + return m_scheme < other.m_scheme; +} + +bool FilePath::operator<=(const FilePath &other) const +{ + return !(other < *this); +} + +bool FilePath::operator>(const FilePath &other) const +{ + return other < *this; +} + +bool FilePath::operator>=(const FilePath &other) const +{ + return !(*this < other); +} + +FilePath FilePath::operator+(const QString &s) const +{ + FilePath res = *this; + res.m_data += s; + return res; +} + +/// \returns whether FilePath is a child of \a s +bool FilePath::isChildOf(const FilePath &s) const +{ + if (s.isEmpty()) + return false; + if (!m_data.startsWith(s.m_data, caseSensitivity())) + return false; + if (m_data.size() <= s.m_data.size()) + return false; + // s is root, '/' was already tested in startsWith + if (s.m_data.endsWith(QLatin1Char('/'))) + return true; + // s is a directory, next character should be '/' (/tmpdir is NOT a child of /tmp) + return m_data.at(s.m_data.size()) == QLatin1Char('/'); +} + +/// \overload +bool FilePath::isChildOf(const QDir &dir) const +{ + return isChildOf(FilePath::fromString(dir.absolutePath())); +} + +/// \returns whether FilePath startsWith \a s +bool FilePath::startsWith(const QString &s) const +{ + return m_data.startsWith(s, caseSensitivity()); +} + +/// \returns whether FilePath endsWith \a s +bool FilePath::endsWith(const QString &s) const +{ + return m_data.endsWith(s, caseSensitivity()); +} + +/// \returns the relativeChildPath of FilePath to parent if FilePath is a child of parent +/// \note returns a empty FilePath if FilePath is not a child of parent +/// That is, this never returns a path starting with "../" +FilePath FilePath::relativeChildPath(const FilePath &parent) const +{ + FilePath res; + if (isChildOf(parent)) + res.m_data = m_data.mid(parent.m_data.size() + 1, -1); + return res; +} + +/// \returns the relativePath of FilePath to given \a anchor. +/// Both, FilePath and anchor may be files or directories. +/// Example usage: +/// +/// \code +/// FilePath filePath("/foo/b/ar/file.txt"); +/// FilePath relativePath = filePath.relativePath("/foo/c"); +/// qDebug() << relativePath +/// \endcode +/// +/// The debug output will be "../b/ar/file.txt". +/// +FilePath FilePath::relativePath(const FilePath &anchor) const +{ + const QFileInfo fileInfo(m_data); + QString absolutePath; + QString filename; + if (fileInfo.isFile()) { + absolutePath = fileInfo.absolutePath(); + filename = fileInfo.fileName(); + } else if (fileInfo.isDir()) { + absolutePath = fileInfo.absoluteFilePath(); + } else { + return {}; + } + const QFileInfo anchorInfo(anchor.m_data); + QString absoluteAnchorPath; + if (anchorInfo.isFile()) + absoluteAnchorPath = anchorInfo.absolutePath(); + else if (anchorInfo.isDir()) + absoluteAnchorPath = anchorInfo.absoluteFilePath(); + else + return {}; + QString relativeFilePath = calcRelativePath(absolutePath, absoluteAnchorPath); + if (!filename.isEmpty()) { + if (!relativeFilePath.isEmpty()) + relativeFilePath += '/'; + relativeFilePath += filename; + } + return FilePath::fromString(relativeFilePath); +} + +/// \returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. +/// Both paths must be an absolute path to a directory. Example usage: +/// +/// \code +/// qDebug() << FilePath::calcRelativePath("/foo/b/ar", "/foo/c"); +/// \endcode +/// +/// The debug output will be "../b/ar". +/// +/// \see FilePath::relativePath +/// +QString FilePath::calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath) +{ + if (absolutePath.isEmpty() || absoluteAnchorPath.isEmpty()) + return QString(); + // TODO using split() instead of parsing the strings by char index is slow + // and needs more memory (but the easiest implementation for now) + const QStringList splits1 = absolutePath.split('/'); + const QStringList splits2 = absoluteAnchorPath.split('/'); + int i = 0; + while (i < splits1.count() && i < splits2.count() && splits1.at(i) == splits2.at(i)) + ++i; + QString relativePath; + int j = i; + bool addslash = false; + while (j < splits2.count()) { + if (!splits2.at(j).isEmpty()) { + if (addslash) + relativePath += '/'; + relativePath += ".."; + addslash = true; + } + ++j; + } + while (i < splits1.count()) { + if (!splits1.at(i).isEmpty()) { + if (addslash) + relativePath += '/'; + relativePath += splits1.at(i); + addslash = true; + } + ++i; + } + return relativePath; +} + +/*! + Returns a path corresponding to the current object on the + same device as \a deviceTemplate. + + Example usage: + \code + localDir = FilePath::fromString("/tmp/workingdir"); + executable = FilePath::fromUrl("docker://123/bin/ls") + realDir = localDir.onDevice(executable) + assert(realDir == FilePath::fromUrl("docker://123/tmp/workingdir")) + \endcode +*/ +FilePath FilePath::onDevice(const FilePath &deviceTemplate) const +{ + FilePath res; + res.m_data = m_data; + res.m_host = deviceTemplate.m_host; + res.m_scheme = deviceTemplate.m_scheme; + return res; +} + +/*! + Returns a FilePath with local path \a newPath on the same device + as the current object. + + Example usage: + \code + devicePath = FilePath::fromString("docker://123/tmp"); + newPath = devicePath.withNewPath("/bin/ls"); + assert(realDir == FilePath::fromUrl("docker://123/bin/ls")) + \endcode +*/ +FilePath FilePath::withNewPath(const QString &newPath) const +{ + FilePath res; + res.m_data = newPath; + res.m_host = m_host; + res.m_scheme = m_scheme; + return res; +} + +/*! + Searched a binary corresponding to this object in the PATH of + the device implied by this object's scheme and host. + + Example usage: + \code + binary = FilePath::fromUrl("docker://123/./make); + fullPath = binary.searchOnDevice(); + assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make")) + \endcode +*/ +FilePath FilePath::searchOnDevice(const FilePaths &dirs) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.searchInPath, return {}); + return s_deviceHooks.searchInPath(*this, dirs); + } + return Environment::systemEnvironment().searchInPath(path(), dirs); +} + +Environment FilePath::deviceEnvironment() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.environment, return {}); + return s_deviceHooks.environment(*this); + } + return Environment::systemEnvironment(); +} + +QString FilePath::formatFilePaths(const QList<FilePath> &files, const QString &separator) +{ + const QStringList nativeFiles = Utils::transform(files, &FilePath::toUserOutput); + return nativeFiles.join(separator); +} + +void FilePath::removeDuplicates(QList<FilePath> &files) +{ + // FIXME: Improve. + QStringList list = Utils::transform<QStringList>(files, &FilePath::toString); + list.removeDuplicates(); + files = Utils::transform(list, &FilePath::fromString); +} + +void FilePath::sort(QList<FilePath> &files) +{ + // FIXME: Improve. + QStringList list = Utils::transform<QStringList>(files, &FilePath::toString); + list.sort(); + files = Utils::transform(list, &FilePath::fromString); +} + +FilePath FilePath::pathAppended(const QString &path) const +{ + FilePath fn = *this; + if (path.isEmpty()) + return fn; + if (!fn.m_data.isEmpty() && !fn.m_data.endsWith(QLatin1Char('/'))) + fn.m_data.append('/'); + fn.m_data.append(path); + return fn; +} + +FilePath FilePath::stringAppended(const QString &str) const +{ + FilePath fn = *this; + fn.m_data.append(str); + return fn; +} + +uint FilePath::hash(uint seed) const +{ + if (Utils::HostOsInfo::fileNameCaseSensitivity() == Qt::CaseInsensitive) + return qHash(m_data.toUpper(), seed); + return qHash(m_data, seed); +} + +QDateTime FilePath::lastModified() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.lastModified, return {}); + return s_deviceHooks.lastModified(*this); + } + return toFileInfo().lastModified(); +} + +QFile::Permissions FilePath::permissions() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.permissions, return {}); + return s_deviceHooks.permissions(*this); + } + return toFileInfo().permissions(); +} + +OsType FilePath::osType() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.osType, return {}); + return s_deviceHooks.osType(*this); + } + return HostOsInfo::hostOs(); +} + +bool FilePath::removeFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.removeFile, return false); + return s_deviceHooks.removeFile(*this); + } + return QFile::remove(path()); +} + +/*! + Removes the directory this filePath refers too and its subdirectories recursively. + + \note The \a error parameter is optional. + + Returns whether the operation succeeded. +*/ +bool FilePath::removeRecursively(QString *error) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.removeRecursively, return false); + return s_deviceHooks.removeRecursively(*this); + } + return removeRecursivelyLocal(*this, error); +} + +bool FilePath::copyFile(const FilePath &target) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.copyFile, return false); + return s_deviceHooks.copyFile(*this, target); + } + return QFile::copy(path(), target.path()); +} + +bool FilePath::renameFile(const FilePath &target) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.renameFile, return false); + return s_deviceHooks.renameFile(*this, target); + } + return QFile::rename(path(), target.path()); +} + +QTextStream &operator<<(QTextStream &s, const FilePath &fn) +{ + return s << fn.toString(); +} + +} // namespace Utils + +std::hash<Utils::FilePath>::result_type + std::hash<Utils::FilePath>::operator()(const std::hash<Utils::FilePath>::argument_type &fn) const +{ + if (fn.caseSensitivity() == Qt::CaseInsensitive) + return hash<string>()(fn.toString().toUpper().toStdString()); + return hash<string>()(fn.toString().toStdString()); +} diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h new file mode 100644 index 0000000000..d53357057d --- /dev/null +++ b/src/libs/utils/filepath.h @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "utils_global.h" + +#include "hostosinfo.h" + +#include <QDir> +#include <QMetaType> + +#include <functional> +#include <memory> + +QT_BEGIN_NAMESPACE +class QDateTime; +class QDebug; +class QFileInfo; +class QUrl; +QT_END_NAMESPACE + +class tst_fileutils; // This becomes a friend of Utils::FilePath for testing private methods. + +namespace Utils { + +class Environment; + +class QTCREATOR_UTILS_EXPORT FilePath +{ +public: + FilePath(); + + static FilePath fromString(const QString &filepath); + static FilePath fromFileInfo(const QFileInfo &info); + static FilePath fromStringWithExtension(const QString &filepath, const QString &defaultExtension); + static FilePath fromUserInput(const QString &filepath); + static FilePath fromUtf8(const char *filepath, int filepathSize = -1); + static FilePath fromVariant(const QVariant &variant); + + QString toString() const; + FilePath onDevice(const FilePath &deviceTemplate) const; + FilePath withNewPath(const QString &newPath) const; + + QFileInfo toFileInfo() const; + QVariant toVariant() const; + QDir toDir() const; + + QString toUserOutput() const; + QString shortNativePath() const; + + QString fileName() const; + QString fileNameWithPathComponents(int pathComponents) const; + + QString baseName() const; + QString completeBaseName() const; + QString suffix() const; + QString completeSuffix() const; + + QString scheme() const { return m_scheme; } + void setScheme(const QString &scheme); + + QString host() const { return m_host; } + void setHost(const QString &host); + + QString path() const { return m_data; } + void setPath(const QString &path) { m_data = path; } + + bool needsDevice() const; + bool exists() const; + + bool isWritablePath() const { return isWritableDir(); } // Remove. + bool isWritableDir() const; + bool isWritableFile() const; + bool ensureWritableDir() const; + bool ensureExistingFile() const; + bool isExecutableFile() const; + bool isReadableFile() const; + bool isReadableDir() const; + bool isRelativePath() const; + bool isAbsolutePath() const { return !isRelativePath(); } + bool isFile() const; + bool isDir() const; + + bool createDir() const; + QList<FilePath> dirEntries(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort = QDir::NoSort) const; + QList<FilePath> dirEntries(QDir::Filters filters) const; + QByteArray fileContents(qint64 maxSize = -1, qint64 offset = 0) const; + bool writeFileContents(const QByteArray &data) const; + + FilePath parentDir() const; + FilePath absolutePath() const; + FilePath absoluteFilePath() const; + FilePath absoluteFilePath(const FilePath &tail) const; + FilePath absoluteFromRelativePath(const FilePath &anchor) const; + + bool operator==(const FilePath &other) const; + bool operator!=(const FilePath &other) const; + bool operator<(const FilePath &other) const; + bool operator<=(const FilePath &other) const; + bool operator>(const FilePath &other) const; + bool operator>=(const FilePath &other) const; + FilePath operator+(const QString &s) const; + + bool isChildOf(const FilePath &s) const; + bool isChildOf(const QDir &dir) const; + bool startsWith(const QString &s) const; + bool endsWith(const QString &s) const; + + bool isNewerThan(const QDateTime &timeStamp) const; + QDateTime lastModified() const; + QFile::Permissions permissions() const; + OsType osType() const; + bool removeFile() const; + bool removeRecursively(QString *error = nullptr) const; + bool copyFile(const FilePath &target) const; + bool renameFile(const FilePath &target) const; + + Qt::CaseSensitivity caseSensitivity() const; + + FilePath relativeChildPath(const FilePath &parent) const; + FilePath relativePath(const FilePath &anchor) const; + FilePath pathAppended(const QString &str) const; + FilePath stringAppended(const QString &str) const; + FilePath resolvePath(const QString &fileName) const; + FilePath cleanPath() const; + + FilePath canonicalPath() const; + FilePath symLinkTarget() const; + FilePath resolveSymlinks() const; + FilePath withExecutableSuffix() const; + + FilePath operator/(const QString &str) const; + + void clear(); + bool isEmpty() const; + + uint hash(uint seed) const; + + // NOTE: Most FilePath operations on FilePath created from URL currently + // do not work. Among the working are .toVariant() and .toUrl(). + static FilePath fromUrl(const QUrl &url); + QUrl toUrl() const; + + FilePath searchOnDevice(const QList<FilePath> &dirs) const; + Environment deviceEnvironment() const; + + static QString formatFilePaths(const QList<FilePath> &files, const QString &separator); + static void removeDuplicates(QList<FilePath> &files); + static void sort(QList<FilePath> &files); + + static QList<FilePath> filterEntriesHelper(const FilePath &base, + const QStringList &entries, + const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort); +private: + friend class ::tst_fileutils; + static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath); + + QString m_scheme; + QString m_host; + QString m_data; +}; + +using FilePaths = QList<FilePath>; + +} // namespace Utils + +QT_BEGIN_NAMESPACE +QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug dbg, const Utils::FilePath &c); +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(Utils::FilePath) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index fceca70937..54f3c1ddf7 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -57,438 +57,9 @@ #include "fileutils_mac.h" #endif -QT_BEGIN_NAMESPACE -QDebug operator<<(QDebug dbg, const Utils::FilePath &c) -{ - return dbg << c.toUserOutput(); -} - -QT_END_NAMESPACE - 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) -{ - QTC_ASSERT(!filePath.needsDevice(), return false); - QFileInfo fileInfo = filePath.toFileInfo(); - if (!fileInfo.exists() && !fileInfo.isSymLink()) - return true; - QFile::setPermissions(filePath.toString(), fileInfo.permissions() | QFile::WriteUser); - if (fileInfo.isDir()) { - QDir dir(filePath.toString()); - dir.setPath(dir.canonicalPath()); - if (dir.isRoot()) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Refusing to remove root directory."); - } - return false; - } - if (dir.path() == QDir::home().canonicalPath()) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Refusing to remove your home directory."); - } - return false; - } - - const QStringList fileNames = dir.entryList( - QDir::Files | QDir::Hidden | QDir::System | QDir::Dirs | QDir::NoDotAndDotDot); - for (const QString &fileName : fileNames) { - if (!removeRecursivelyLocal(filePath / fileName, error)) - return false; - } - if (!QDir::root().rmdir(dir.path())) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove directory \"%1\".") - .arg(filePath.toUserOutput()); - } - return false; - } - } else { - if (!QFile::remove(filePath.toString())) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove file \"%1\".") - .arg(filePath.toUserOutput()); - } - return false; - } - } - return true; -} - -/*! - 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; - book 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 QFileInfo &src, const QFileInfo &dest, QString *error) { - if (!QFile::copy(src.filePath(), dest.filePath())) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Could not copy file \"%1\" to \"%2\".") - .arg(FilePath::fromFileInfo(src).toUserOutput(), - FilePath::fromFileInfo(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); -} - -/*! - If this is a directory, the function will recursively check all files and return - true if one of them is newer than \a timeStamp. If this is a single file, true will - be returned if the file is newer than \a timeStamp. - - Returns whether at least one file in \a filePath has a newer date than - \a timeStamp. -*/ -bool FilePath::isNewerThan(const QDateTime &timeStamp) const -{ - if (!exists() || lastModified() >= timeStamp) - return true; - if (isDir()) { - const FilePaths dirContents = dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); - for (const FilePath &entry : dirContents) { - if (entry.isNewerThan(timeStamp)) - return true; - } - } - return false; -} - -Qt::CaseSensitivity FilePath::caseSensitivity() const -{ - if (m_scheme.isEmpty()) - return HostOsInfo::fileNameCaseSensitivity(); - - // FIXME: This could or possibly should the target device's file name case sensitivity - // into account by diverting to IDevice. However, as this is expensive and we are - // in time-critical path here, we go with "good enough" for now: - // The first approximation is "Anything unusual is not case sensitive" - return Qt::CaseSensitive; -} - -/*! - Recursively resolves symlinks if this is a symlink. - To resolve symlinks anywhere in the path, see canonicalPath. - Unlike QFileInfo::canonicalFilePath(), this function will still return the expected deepest - target file even if the symlink is dangling. - - \note Maximum recursion depth == 16. - - Returns the symlink target file path. -*/ -FilePath FilePath::resolveSymlinks() const -{ - FilePath current = *this; - int links = 16; - while (links--) { - const FilePath target = current.symLinkTarget(); - if (target.isEmpty()) - return current; - current = target; - } - return current; -} - -/*! - Recursively resolves possibly present symlinks in this file name. - Unlike QFileInfo::canonicalFilePath(), this function will not return an empty - string if path doesn't exist. - - Returns the canonical path. -*/ -FilePath FilePath::canonicalPath() const -{ - if (needsDevice()) { - // FIXME: Not a full solution, but it stays on the right device. - return *this; - } - const QString result = toFileInfo().canonicalFilePath(); - if (result.isEmpty()) - return *this; - return FilePath::fromString(result); -} - -FilePath FilePath::operator/(const QString &str) const -{ - return pathAppended(str); -} - -void FilePath::clear() -{ - m_data.clear(); - m_host.clear(); - m_scheme.clear(); -} - -bool FilePath::isEmpty() const -{ - return m_data.isEmpty(); -} - -/*! - Like QDir::toNativeSeparators(), but use prefix '~' instead of $HOME on unix systems when an - absolute path is given. - - Returns the possibly shortened path with native separators. -*/ -QString FilePath::shortNativePath() const -{ - if (HostOsInfo::isAnyUnixHost()) { - const FilePath home = FileUtils::homePath(); - if (isChildOf(home)) { - return QLatin1Char('~') + QDir::separator() - + QDir::toNativeSeparators(relativeChildPath(home).toString()); - } - } - 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) -{ - const QString filePath = path.toString(); - return QFile::setPermissions(filePath, QFile::permissions(filePath) | QFile::WriteUser); -} - -// makes sure that capitalization of directories is canonical on Windows and OS X. -// This mimics the logic in QDeclarative_isFileCaseCorrect -QString FileUtils::normalizePathName(const QString &name) -{ -#ifdef Q_OS_WIN - const QString nativeSeparatorName(QDir::toNativeSeparators(name)); - const auto nameC = reinterpret_cast<LPCTSTR>(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<const ushort *>(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 -{ - return isRelativePathHelper(m_data, osType()); -} - -FilePath FilePath::resolvePath(const QString &fileName) const -{ - if (FileUtils::isAbsolutePath(fileName)) - return FilePath::fromString(QDir::cleanPath(fileName)); - FilePath result = *this; - result.setPath(QDir::cleanPath(m_data + '/' + fileName)); - return result; -} - -FilePath FilePath::cleanPath() const -{ - FilePath result = *this; - result.setPath(QDir::cleanPath(result.path())); - 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(); -} - -// Copied from qfilesystemengine_win.cpp -#ifdef Q_OS_WIN - -// File ID for Windows up to version 7. -static inline QByteArray fileIdWin7(HANDLE handle) -{ - BY_HANDLE_FILE_INFORMATION info; - if (GetFileInformationByHandle(handle, &info)) { - char buffer[sizeof "01234567:0123456701234567\0"]; - qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", - info.dwVolumeSerialNumber, - info.nFileIndexHigh, - info.nFileIndexLow); - return QByteArray(buffer); - } - return QByteArray(); -} - -// File ID for Windows starting from version 8. -static QByteArray fileIdWin8(HANDLE handle) -{ - QByteArray result; - FILE_ID_INFO infoEx; - if (GetFileInformationByHandleEx(handle, - static_cast<FILE_INFO_BY_HANDLE_CLASS>(18), // FileIdInfo in Windows 8 - &infoEx, sizeof(FILE_ID_INFO))) { - result = QByteArray::number(infoEx.VolumeSerialNumber, 16); - result += ':'; - // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. - result += QByteArray(reinterpret_cast<const char *>(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); - } - return result; -} - -static QByteArray fileIdWin(HANDLE fHandle) -{ - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? - fileIdWin8(HANDLE(fHandle)) : fileIdWin7(HANDLE(fHandle)); -} -#endif - -QByteArray FileUtils::fileId(const FilePath &fileName) -{ - QByteArray result; - -#ifdef Q_OS_WIN - const HANDLE handle = - CreateFile((wchar_t*)fileName.toUserOutput().utf16(), 0, - FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (handle != INVALID_HANDLE_VALUE) { - result = fileIdWin(handle); - CloseHandle(handle); - } -#else // Copied from qfilesystemengine_unix.cpp - if (Q_UNLIKELY(fileName.isEmpty())) - return result; - - QT_STATBUF statResult; - if (QT_STAT(fileName.toString().toLocal8Bit().constData(), &statResult)) - return result; - result = QByteArray::number(quint64(statResult.st_dev), 16); - result += ':'; - result += QByteArray::number(quint64(statResult.st_ino)); -#endif - return result; -} - -FilePath FileUtils::homePath() -{ - return FilePath::fromString(QDir::cleanPath(QDir::homePath())); -} - -bool FileUtils::renameFile(const FilePath &srcFilePath, const FilePath &tgtFilePath) -{ - QTC_ASSERT(!srcFilePath.needsDevice(), return false); - QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false); - return QFile::rename(srcFilePath.path(), tgtFilePath.path()); -} +// FileReader QByteArray FileReader::fetchQrc(const QString &fileName) { @@ -544,6 +115,8 @@ bool FileReader::fetch(const FilePath &filePath, QIODevice::OpenMode mode, QWidg } #endif // QT_GUI_LIB +// FileSaver + FileSaverBase::FileSaverBase() = default; FileSaverBase::~FileSaverBase() = default; @@ -619,6 +192,7 @@ bool FileSaverBase::setResult(QXmlStreamWriter *stream) return setResult(!stream->hasError()); } +// FileSaver FileSaver::FileSaver(const FilePath &filePath, QIODevice::OpenMode mode) { @@ -692,904 +266,6 @@ TempFileSaver::~TempFileSaver() QFile::remove(m_filePath.toString()); } -/*! \class Utils::FilePath - - \brief The FilePath class is a light-weight convenience class for filenames. - - On windows filenames are compared case insensitively. -*/ - -FilePath::FilePath() -{ -} - -/// Constructs a FilePath from \a info -FilePath FilePath::fromFileInfo(const QFileInfo &info) -{ - return FilePath::fromString(info.absoluteFilePath()); -} - -/// \returns a QFileInfo -QFileInfo FilePath::toFileInfo() const -{ - return QFileInfo(m_data); -} - -FilePath FilePath::fromUrl(const QUrl &url) -{ - FilePath fn; - fn.m_scheme = url.scheme(); - fn.m_host = url.host(); - fn.m_data = url.path(); - return fn; -} - -/// \returns a QString for passing on to QString based APIs -QString FilePath::toString() const -{ - if (m_scheme.isEmpty()) - return m_data; - if (m_data.startsWith('/')) - return m_scheme + "://" + m_host + m_data; - return m_scheme + "://" + m_host + "/./" + m_data; -} - -QUrl FilePath::toUrl() const -{ - QUrl url; - url.setScheme(m_scheme); - url.setHost(m_host); - url.setPath(m_data); - return url; -} - -void FilePath::setDeviceFileHooks(const DeviceFileHooks &hooks) -{ - s_deviceHooks = hooks; -} - -/// \returns a QString to display to the user -/// Converts the separators to the native format -QString FilePath::toUserOutput() const -{ - if (m_scheme.isEmpty()) - return QDir::toNativeSeparators(m_data); - return toString(); -} - -QString FilePath::fileName() const -{ - const QChar slash = QLatin1Char('/'); - return m_data.mid(m_data.lastIndexOf(slash) + 1); -} - -QString FilePath::fileNameWithPathComponents(int pathComponents) const -{ - if (pathComponents < 0) - return m_data; - const QChar slash = QLatin1Char('/'); - int i = m_data.lastIndexOf(slash); - if (pathComponents == 0 || i == -1) - return m_data.mid(i + 1); - int component = i + 1; - // skip adjacent slashes - while (i > 0 && m_data.at(--i) == slash) - ; - while (i >= 0 && --pathComponents >= 0) { - i = m_data.lastIndexOf(slash, i); - component = i + 1; - while (i > 0 && m_data.at(--i) == slash) - ; - } - - // If there are no more slashes before the found one, return the entire string - if (i > 0 && m_data.lastIndexOf(slash, i) != -1) - return m_data.mid(component); - return m_data; -} - -/// \returns the base name of the file without the path. -/// -/// The base name consists of all characters in the file up to -/// (but not including) the first '.' character. - -QString FilePath::baseName() const -{ - const QString &name = fileName(); - return name.left(name.indexOf('.')); -} - -/// \returns the complete base name of the file without the path. -/// -/// The complete base name consists of all characters in the file up to -/// (but not including) the last '.' character - -QString FilePath::completeBaseName() const -{ - const QString &name = fileName(); - return name.left(name.lastIndexOf('.')); -} - -/// \returns the suffix (extension) of the file. -/// -/// The suffix consists of all characters in the file after -/// (but not including) the last '.'. - -QString FilePath::suffix() const -{ - const QString &name = fileName(); - const int index = name.lastIndexOf('.'); - if (index >= 0) - return name.mid(index + 1); - return {}; -} - -/// \returns the complete suffix (extension) of the file. -/// -/// The complete suffix consists of all characters in the file after -/// (but not including) the first '.'. - -QString FilePath::completeSuffix() const -{ - const QString &name = fileName(); - const int index = name.indexOf('.'); - if (index >= 0) - return name.mid(index + 1); - return {}; -} - -void FilePath::setScheme(const QString &scheme) -{ - QTC_CHECK(!scheme.contains('/')); - m_scheme = scheme; -} - -void FilePath::setHost(const QString &host) -{ - QTC_CHECK(!host.contains('/')); - m_host = host; -} - - -/// \returns a bool indicating whether a file with this -/// FilePath exists. -bool FilePath::exists() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.exists, return false); - return s_deviceHooks.exists(*this); - } - return !isEmpty() && QFileInfo::exists(m_data); -} - -/// \returns a bool indicating whether a path is writable. -bool FilePath::isWritableDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isWritableDir, return false); - return s_deviceHooks.isWritableDir(*this); - } - const QFileInfo fi{m_data}; - return exists() && fi.isDir() && fi.isWritable(); -} - -bool FilePath::isWritableFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isWritableFile, return false); - return s_deviceHooks.isWritableFile(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isWritable() && !fi.isDir(); -} - -bool FilePath::ensureWritableDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.ensureWritableDir, return false); - return s_deviceHooks.ensureWritableDir(*this); - } - const QFileInfo fi{m_data}; - if (exists() && fi.isDir() && fi.isWritable()) - return true; - return QDir().mkpath(m_data); -} - -bool FilePath::ensureExistingFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.ensureExistingFile, return false); - return s_deviceHooks.ensureExistingFile(*this); - } - QFile f(m_data); - if (f.exists()) - return true; - f.open(QFile::WriteOnly); - f.close(); - return f.exists(); -} - -bool FilePath::isExecutableFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isExecutableFile, return false); - return s_deviceHooks.isExecutableFile(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isExecutable() && !fi.isDir(); -} - -bool FilePath::isReadableFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isReadableFile, return false); - return s_deviceHooks.isReadableFile(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isReadable() && !fi.isDir(); -} - -bool FilePath::isReadableDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isReadableDir, return false); - return s_deviceHooks.isReadableDir(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isReadable() && fi.isDir(); -} - -bool FilePath::isFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isFile, return false); - return s_deviceHooks.isFile(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isFile(); -} - -bool FilePath::isDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isDir, return false); - return s_deviceHooks.isDir(*this); - } - const QFileInfo fi{m_data}; - return fi.exists() && fi.isDir(); -} - -bool FilePath::createDir() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.createDir, return false); - return s_deviceHooks.createDir(*this); - } - QDir dir(m_data); - return dir.mkpath(dir.absolutePath()); -} - -FilePaths FilePath::dirEntries(const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.dirEntries, return {}); - return s_deviceHooks.dirEntries(*this, nameFilters, filters, sort); - } - - const QFileInfoList entryInfoList = QDir(m_data).entryInfoList(nameFilters, filters, sort); - return Utils::transform(entryInfoList, &FilePath::fromFileInfo); -} - -FilePaths FilePath::filterEntriesHelper(const FilePath &base, - const QStringList &entries, - const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort) -{ - const QList<QRegularExpression> nameRegexps = transform(nameFilters, [](const QString &filter) { - QRegularExpression re; - re.setPattern(QRegularExpression::wildcardToRegularExpression(filter)); - QTC_CHECK(re.isValid()); - return re; - }); - - const auto nameMatches = [&nameRegexps](const QString &fileName) { - for (const QRegularExpression &re : nameRegexps) { - const QRegularExpressionMatch match = re.match(fileName); - if (match.hasMatch()) - return true; - } - return false; - }; - - // FIXME: Handle sort and filters. For now bark on unsupported options. - QTC_CHECK(filters == QDir::NoFilter); - QTC_CHECK(sort == QDir::NoSort); - - FilePaths result; - for (const QString &entry : entries) { - if (!nameMatches(entry)) - continue; - result.append(base.pathAppended(entry)); - } - return result; -} - -QList<FilePath> FilePath::dirEntries(QDir::Filters filters) const -{ - return dirEntries({}, filters); -} - -QByteArray FilePath::fileContents(qint64 maxSize, qint64 offset) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.fileContents, return {}); - return s_deviceHooks.fileContents(*this, maxSize, offset); - } - - const QString path = toString(); - QFile f(path); - if (!f.exists()) - return {}; - - if (!f.open(QFile::ReadOnly)) - return {}; - - if (offset != 0) - f.seek(offset); - - if (maxSize != -1) - return f.read(maxSize); - - return f.readAll(); -} - -bool FilePath::writeFileContents(const QByteArray &data) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.writeFileContents, return {}); - return s_deviceHooks.writeFileContents(*this, data); - } - - QFile file(path()); - QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return false); - qint64 res = file.write(data); - return res == data.size(); -} - -bool FilePath::needsDevice() const -{ - return !m_scheme.isEmpty(); -} - -/// \returns an empty FilePath if this is not a symbolic linl -FilePath FilePath::symLinkTarget() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.symLinkTarget, return {}); - return s_deviceHooks.symLinkTarget(*this); - } - const QFileInfo info(m_data); - if (!info.isSymLink()) - return {}; - return FilePath::fromString(info.symLinkTarget()); -} - -FilePath FilePath::withExecutableSuffix() const -{ - FilePath res = *this; - res.setPath(OsSpecificAspects::withExecutableSuffix(osType(), m_data)); - return res; -} - -/// Find the parent directory of a given directory. - -/// Returns an empty FilePath if the current directory is already -/// a root level directory. - -/// \returns \a FilePath with the last segment removed. -FilePath FilePath::parentDir() const -{ - const QString basePath = path(); - if (basePath.isEmpty()) - return FilePath(); - - const QDir base(basePath); - if (base.isRoot()) - return FilePath(); - - const QString path = basePath + QLatin1String("/.."); - const QString parent = QDir::cleanPath(path); - QTC_ASSERT(parent != path, return FilePath()); - - FilePath result = *this; - result.setPath(parent); - return result; -} - -FilePath FilePath::absolutePath() const -{ - FilePath result = *this; - result.m_data = QFileInfo(m_data).absolutePath(); - return result; -} - -FilePath FilePath::absoluteFilePath() const -{ - FilePath result = *this; - result.m_data = QFileInfo(m_data).absoluteFilePath(); - return result; -} - -FilePath FilePath::absoluteFilePath(const FilePath &tail) const -{ - if (isRelativePathHelper(tail.m_data, osType())) - return pathAppended(tail.m_data); - return tail; -} - -/// Constructs an absolute FilePath from this path which -/// is interpreted as being relative to \a anchor. -FilePath FilePath::absoluteFromRelativePath(const FilePath &anchor) const -{ - QDir anchorDir = QFileInfo(anchor.m_data).absoluteDir(); - QString absoluteFilePath = QFileInfo(anchorDir, m_data).canonicalFilePath(); - return FilePath::fromString(absoluteFilePath); -} - -/// Constructs a FilePath from \a filename -/// \a filename is not checked for validity. -FilePath FilePath::fromString(const QString &filename) -{ - FilePath fn; - if (filename.startsWith('/')) { - fn.m_data = filename; // fast track: absolute local paths - } else { - int pos1 = filename.indexOf("://"); - if (pos1 >= 0) { - fn.m_scheme = filename.left(pos1); - pos1 += 3; - int pos2 = filename.indexOf('/', pos1); - if (pos2 == -1) { - fn.m_data = filename.mid(pos1); - } else { - fn.m_host = filename.mid(pos1, pos2 - pos1); - fn.m_data = filename.mid(pos2); - } - if (fn.m_data.startsWith("/./")) - fn.m_data = fn.m_data.mid(3); - } else { - fn.m_data = filename; // treat everything else as local, too. - } - } - return fn; -} - -/// Constructs a FilePath from \a filePath. The \a defaultExtension is appended -/// to \a filename if that does not have an extension already. -/// \a filePath is not checked for validity. -FilePath FilePath::fromStringWithExtension(const QString &filepath, const QString &defaultExtension) -{ - if (filepath.isEmpty() || defaultExtension.isEmpty()) - return FilePath::fromString(filepath); - - QString rc = filepath; - QFileInfo fi(filepath); - // Add extension unless user specified something else - const QChar dot = QLatin1Char('.'); - if (!fi.fileName().contains(dot)) { - if (!defaultExtension.startsWith(dot)) - rc += dot; - rc += defaultExtension; - } - return FilePath::fromString(rc); -} - -/// Constructs a FilePath from \a filePath -/// \a filePath is only passed through QDir::fromNativeSeparators -FilePath FilePath::fromUserInput(const QString &filePath) -{ - QString clean = QDir::fromNativeSeparators(filePath); - if (clean.startsWith(QLatin1String("~/"))) - return FileUtils::homePath().pathAppended(clean.mid(2)); - return FilePath::fromString(clean); -} - -/// Constructs a FilePath from \a filePath, which is encoded as UTF-8. -/// \a filePath is not checked for validity. -FilePath FilePath::fromUtf8(const char *filename, int filenameSize) -{ - return FilePath::fromString(QString::fromUtf8(filename, filenameSize)); -} - -FilePath FilePath::fromVariant(const QVariant &variant) -{ - if (variant.type() == QVariant::Url) - return FilePath::fromUrl(variant.toUrl()); - return FilePath::fromString(variant.toString()); -} - -QVariant FilePath::toVariant() const -{ - return toString(); -} - -QDir FilePath::toDir() const -{ - return QDir(m_data); -} - -bool FilePath::operator==(const FilePath &other) const -{ - return QString::compare(m_data, other.m_data, caseSensitivity()) == 0 - && m_host == other.m_host - && m_scheme == other.m_scheme; -} - -bool FilePath::operator!=(const FilePath &other) const -{ - return !(*this == other); -} - -bool FilePath::operator<(const FilePath &other) const -{ - const int cmp = QString::compare(m_data, other.m_data, caseSensitivity()); - if (cmp != 0) - return cmp < 0; - if (m_host != other.m_host) - return m_host < other.m_host; - return m_scheme < other.m_scheme; -} - -bool FilePath::operator<=(const FilePath &other) const -{ - return !(other < *this); -} - -bool FilePath::operator>(const FilePath &other) const -{ - return other < *this; -} - -bool FilePath::operator>=(const FilePath &other) const -{ - return !(*this < other); -} - -FilePath FilePath::operator+(const QString &s) const -{ - FilePath res = *this; - res.m_data += s; - return res; -} - -/// \returns whether FilePath is a child of \a s -bool FilePath::isChildOf(const FilePath &s) const -{ - if (s.isEmpty()) - return false; - if (!m_data.startsWith(s.m_data, caseSensitivity())) - return false; - if (m_data.size() <= s.m_data.size()) - return false; - // s is root, '/' was already tested in startsWith - if (s.m_data.endsWith(QLatin1Char('/'))) - return true; - // s is a directory, next character should be '/' (/tmpdir is NOT a child of /tmp) - return m_data.at(s.m_data.size()) == QLatin1Char('/'); -} - -/// \overload -bool FilePath::isChildOf(const QDir &dir) const -{ - return isChildOf(FilePath::fromString(dir.absolutePath())); -} - -/// \returns whether FilePath startsWith \a s -bool FilePath::startsWith(const QString &s) const -{ - return m_data.startsWith(s, caseSensitivity()); -} - -/// \returns whether FilePath endsWith \a s -bool FilePath::endsWith(const QString &s) const -{ - return m_data.endsWith(s, caseSensitivity()); -} - -/// \returns the relativeChildPath of FilePath to parent if FilePath is a child of parent -/// \note returns a empty FilePath if FilePath is not a child of parent -/// That is, this never returns a path starting with "../" -FilePath FilePath::relativeChildPath(const FilePath &parent) const -{ - FilePath res; - if (isChildOf(parent)) - res.m_data = m_data.mid(parent.m_data.size() + 1, -1); - return res; -} - -/// \returns the relativePath of FilePath to given \a anchor. -/// Both, FilePath and anchor may be files or directories. -/// Example usage: -/// -/// \code -/// FilePath filePath("/foo/b/ar/file.txt"); -/// FilePath relativePath = filePath.relativePath("/foo/c"); -/// qDebug() << relativePath -/// \endcode -/// -/// The debug output will be "../b/ar/file.txt". -/// -FilePath FilePath::relativePath(const FilePath &anchor) const -{ - const QFileInfo fileInfo(m_data); - QString absolutePath; - QString filename; - if (fileInfo.isFile()) { - absolutePath = fileInfo.absolutePath(); - filename = fileInfo.fileName(); - } else if (fileInfo.isDir()) { - absolutePath = fileInfo.absoluteFilePath(); - } else { - return {}; - } - const QFileInfo anchorInfo(anchor.m_data); - QString absoluteAnchorPath; - if (anchorInfo.isFile()) - absoluteAnchorPath = anchorInfo.absolutePath(); - else if (anchorInfo.isDir()) - absoluteAnchorPath = anchorInfo.absoluteFilePath(); - else - return {}; - QString relativeFilePath = calcRelativePath(absolutePath, absoluteAnchorPath); - if (!filename.isEmpty()) { - if (!relativeFilePath.isEmpty()) - relativeFilePath += '/'; - relativeFilePath += filename; - } - return FilePath::fromString(relativeFilePath); -} - -/// \returns the relativePath of \a absolutePath to given \a absoluteAnchorPath. -/// Both paths must be an absolute path to a directory. Example usage: -/// -/// \code -/// qDebug() << FilePath::calcRelativePath("/foo/b/ar", "/foo/c"); -/// \endcode -/// -/// The debug output will be "../b/ar". -/// -/// \see FilePath::relativePath -/// -QString FilePath::calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath) -{ - if (absolutePath.isEmpty() || absoluteAnchorPath.isEmpty()) - return QString(); - // TODO using split() instead of parsing the strings by char index is slow - // and needs more memory (but the easiest implementation for now) - const QStringList splits1 = absolutePath.split('/'); - const QStringList splits2 = absoluteAnchorPath.split('/'); - int i = 0; - while (i < splits1.count() && i < splits2.count() && splits1.at(i) == splits2.at(i)) - ++i; - QString relativePath; - int j = i; - bool addslash = false; - while (j < splits2.count()) { - if (!splits2.at(j).isEmpty()) { - if (addslash) - relativePath += '/'; - relativePath += ".."; - addslash = true; - } - ++j; - } - while (i < splits1.count()) { - if (!splits1.at(i).isEmpty()) { - if (addslash) - relativePath += '/'; - relativePath += splits1.at(i); - addslash = true; - } - ++i; - } - return relativePath; -} - -/*! - Returns a path corresponding to the current object on the - same device as \a deviceTemplate. - - Example usage: - \code - localDir = FilePath::fromString("/tmp/workingdir"); - executable = FilePath::fromUrl("docker://123/bin/ls") - realDir = localDir.onDevice(executable) - assert(realDir == FilePath::fromUrl("docker://123/tmp/workingdir")) - \endcode -*/ -FilePath FilePath::onDevice(const FilePath &deviceTemplate) const -{ - FilePath res; - res.m_data = m_data; - res.m_host = deviceTemplate.m_host; - res.m_scheme = deviceTemplate.m_scheme; - return res; -} - -/*! - Returns a FilePath with local path \a newPath on the same device - as the current object. - - Example usage: - \code - devicePath = FilePath::fromString("docker://123/tmp"); - newPath = devicePath.withNewPath("/bin/ls"); - assert(realDir == FilePath::fromUrl("docker://123/bin/ls")) - \endcode -*/ -FilePath FilePath::withNewPath(const QString &newPath) const -{ - FilePath res; - res.m_data = newPath; - res.m_host = m_host; - res.m_scheme = m_scheme; - return res; -} - -/*! - Searched a binary corresponding to this object in the PATH of - the device implied by this object's scheme and host. - - Example usage: - \code - binary = FilePath::fromUrl("docker://123/./make); - fullPath = binary.searchOnDevice(); - assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make")) - \endcode -*/ -FilePath FilePath::searchOnDevice(const FilePaths &dirs) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.searchInPath, return {}); - return s_deviceHooks.searchInPath(*this, dirs); - } - return Environment::systemEnvironment().searchInPath(path(), dirs); -} - -Environment FilePath::deviceEnvironment() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.environment, return {}); - return s_deviceHooks.environment(*this); - } - return Environment::systemEnvironment(); -} - -QString FilePath::formatFilePaths(const QList<FilePath> &files, const QString &separator) -{ - const QStringList nativeFiles = Utils::transform(files, &FilePath::toUserOutput); - return nativeFiles.join(separator); -} - -void FilePath::removeDuplicates(QList<FilePath> &files) -{ - // FIXME: Improve. - QStringList list = Utils::transform<QStringList>(files, &FilePath::toString); - list.removeDuplicates(); - files = Utils::transform(list, &FilePath::fromString); -} - -void FilePath::sort(QList<FilePath> &files) -{ - // FIXME: Improve. - QStringList list = Utils::transform<QStringList>(files, &FilePath::toString); - list.sort(); - files = Utils::transform(list, &FilePath::fromString); -} - -FilePath FilePath::pathAppended(const QString &path) const -{ - FilePath fn = *this; - if (path.isEmpty()) - return fn; - if (!fn.m_data.isEmpty() && !fn.m_data.endsWith(QLatin1Char('/'))) - fn.m_data.append('/'); - fn.m_data.append(path); - return fn; -} - -FilePath FilePath::stringAppended(const QString &str) const -{ - FilePath fn = *this; - fn.m_data.append(str); - return fn; -} - -uint FilePath::hash(uint seed) const -{ - if (Utils::HostOsInfo::fileNameCaseSensitivity() == Qt::CaseInsensitive) - return qHash(m_data.toUpper(), seed); - return qHash(m_data, seed); -} - -QDateTime FilePath::lastModified() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.lastModified, return {}); - return s_deviceHooks.lastModified(*this); - } - return toFileInfo().lastModified(); -} - -QFile::Permissions FilePath::permissions() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.permissions, return {}); - return s_deviceHooks.permissions(*this); - } - return toFileInfo().permissions(); -} - -OsType FilePath::osType() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.osType, return {}); - return s_deviceHooks.osType(*this); - } - return HostOsInfo::hostOs(); -} - -bool FilePath::removeFile() const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.removeFile, return false); - return s_deviceHooks.removeFile(*this); - } - return QFile::remove(path()); -} - -/*! - Removes the directory this filePath refers too and its subdirectories recursively. - - \note The \a error parameter is optional. - - Returns whether the operation succeeded. -*/ -bool FilePath::removeRecursively(QString *error) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.removeRecursively, return false); - return s_deviceHooks.removeRecursively(*this); - } - return removeRecursivelyLocal(*this, error); -} - -bool FilePath::copyFile(const FilePath &target) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.copyFile, return false); - return s_deviceHooks.copyFile(*this, target); - } - return QFile::copy(path(), target.path()); -} - -bool FilePath::renameFile(const FilePath &target) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.renameFile, return false); - return s_deviceHooks.renameFile(*this, target); - } - return QFile::rename(path(), target.path()); -} - -QTextStream &operator<<(QTextStream &s, const FilePath &fn) -{ - return s << fn.toString(); -} - #ifdef QT_GUI_LIB FileUtils::CopyAskingForOverwrite::CopyAskingForOverwrite( QWidget *dialogParent, const std::function<void(QFileInfo)> &postOperation) @@ -1652,6 +328,74 @@ FilePaths FileUtils::CopyAskingForOverwrite::files() const } #endif // QT_GUI_LIB +// Copied from qfilesystemengine_win.cpp +#ifdef Q_OS_WIN + +// File ID for Windows up to version 7. +static inline QByteArray fileIdWin7(HANDLE handle) +{ + BY_HANDLE_FILE_INFORMATION info; + if (GetFileInformationByHandle(handle, &info)) { + char buffer[sizeof "01234567:0123456701234567\0"]; + qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", + info.dwVolumeSerialNumber, + info.nFileIndexHigh, + info.nFileIndexLow); + return QByteArray(buffer); + } + return QByteArray(); +} + +// File ID for Windows starting from version 8. +static QByteArray fileIdWin8(HANDLE handle) +{ + QByteArray result; + FILE_ID_INFO infoEx; + if (GetFileInformationByHandleEx(handle, + static_cast<FILE_INFO_BY_HANDLE_CLASS>(18), // FileIdInfo in Windows 8 + &infoEx, sizeof(FILE_ID_INFO))) { + result = QByteArray::number(infoEx.VolumeSerialNumber, 16); + result += ':'; + // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. + result += QByteArray(reinterpret_cast<const char *>(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); + } + return result; +} + +static QByteArray fileIdWin(HANDLE fHandle) +{ + return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? + fileIdWin8(HANDLE(fHandle)) : fileIdWin7(HANDLE(fHandle)); +} +#endif + +QByteArray FileUtils::fileId(const FilePath &fileName) +{ + QByteArray result; + +#ifdef Q_OS_WIN + const HANDLE handle = + CreateFile((wchar_t*)fileName.toUserOutput().utf16(), 0, + FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle != INVALID_HANDLE_VALUE) { + result = fileIdWin(handle); + CloseHandle(handle); + } +#else // Copied from qfilesystemengine_unix.cpp + if (Q_UNLIKELY(fileName.isEmpty())) + return result; + + QT_STATBUF statResult; + if (QT_STAT(fileName.toString().toLocal8Bit().constData(), &statResult)) + return result; + result = QByteArray::number(quint64(statResult.st_dev), 16); + result += ':'; + result += QByteArray::number(quint64(statResult.st_ino)); +#endif + return result; +} + #ifdef Q_OS_WIN template <> void withNtfsPermissions(const std::function<void()> &task) @@ -1664,10 +408,3 @@ void withNtfsPermissions(const std::function<void()> &task) } // namespace Utils -std::hash<Utils::FilePath>::result_type - std::hash<Utils::FilePath>::operator()(const std::hash<Utils::FilePath>::argument_type &fn) const -{ - if (fn.caseSensitivity() == Qt::CaseInsensitive) - return hash<string>()(fn.toString().toUpper().toStdString()); - return hash<string>()(fn.toString().toStdString()); -} diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 1adf0c0657..9901f67876 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -27,6 +27,7 @@ #include "utils_global.h" +#include "filepath.h" #include "hostosinfo.h" #include <QCoreApplication> @@ -40,33 +41,17 @@ #include <functional> #include <memory> -namespace Utils { -class Environment; -class FilePath; -} // Utils - QT_BEGIN_NAMESPACE class QDataStream; -class QDateTime; -class QDir; -class QFile; -class QFileInfo; -class QTemporaryFile; class QTextStream; class QWidget; -QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug dbg, const Utils::FilePath &c); - // for withNtfsPermissions #ifdef Q_OS_WIN extern Q_CORE_EXPORT int qt_ntfs_permission_lookup; #endif - QT_END_NAMESPACE -// tst_fileutils becomes a friend of Utils::FilePath for testing private method -class tst_fileutils; - namespace Utils { class DeviceFileHooks @@ -99,151 +84,6 @@ public: std::function<Environment(const FilePath &)> environment; }; -class QTCREATOR_UTILS_EXPORT FilePath -{ -public: - FilePath(); - - static FilePath fromString(const QString &filepath); - static FilePath fromFileInfo(const QFileInfo &info); - static FilePath fromStringWithExtension(const QString &filepath, const QString &defaultExtension); - static FilePath fromUserInput(const QString &filepath); - static FilePath fromUtf8(const char *filepath, int filepathSize = -1); - static FilePath fromVariant(const QVariant &variant); - - QString toString() const; - FilePath onDevice(const FilePath &deviceTemplate) const; - FilePath withNewPath(const QString &newPath) const; - - QFileInfo toFileInfo() const; - QVariant toVariant() const; - QDir toDir() const; - - QString toUserOutput() const; - QString shortNativePath() const; - - QString fileName() const; - QString fileNameWithPathComponents(int pathComponents) const; - - QString baseName() const; - QString completeBaseName() const; - QString suffix() const; - QString completeSuffix() const; - - QString scheme() const { return m_scheme; } - void setScheme(const QString &scheme); - - QString host() const { return m_host; } - void setHost(const QString &host); - - QString path() const { return m_data; } - void setPath(const QString &path) { m_data = path; } - - bool needsDevice() const; - bool exists() const; - - bool isWritablePath() const { return isWritableDir(); } // Remove. - bool isWritableDir() const; - bool isWritableFile() const; - bool ensureWritableDir() const; - bool ensureExistingFile() const; - bool isExecutableFile() const; - bool isReadableFile() const; - bool isReadableDir() const; - bool isRelativePath() const; - bool isAbsolutePath() const { return !isRelativePath(); } - bool isFile() const; - bool isDir() const; - - bool createDir() const; - QList<FilePath> dirEntries(const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort = QDir::NoSort) const; - QList<FilePath> dirEntries(QDir::Filters filters) const; - QByteArray fileContents(qint64 maxSize = -1, qint64 offset = 0) const; - bool writeFileContents(const QByteArray &data) const; - - FilePath parentDir() const; - FilePath absolutePath() const; - FilePath absoluteFilePath() const; - FilePath absoluteFilePath(const FilePath &tail) const; - FilePath absoluteFromRelativePath(const FilePath &anchor) const; - - bool operator==(const FilePath &other) const; - bool operator!=(const FilePath &other) const; - bool operator<(const FilePath &other) const; - bool operator<=(const FilePath &other) const; - bool operator>(const FilePath &other) const; - bool operator>=(const FilePath &other) const; - FilePath operator+(const QString &s) const; - - bool isChildOf(const FilePath &s) const; - bool isChildOf(const QDir &dir) const; - bool startsWith(const QString &s) const; - bool endsWith(const QString &s) const; - - bool isNewerThan(const QDateTime &timeStamp) const; - QDateTime lastModified() const; - QFile::Permissions permissions() const; - OsType osType() const; - bool removeFile() const; - bool removeRecursively(QString *error = nullptr) const; - bool copyFile(const FilePath &target) const; - bool renameFile(const FilePath &target) const; - - Qt::CaseSensitivity caseSensitivity() const; - - FilePath relativeChildPath(const FilePath &parent) const; - FilePath relativePath(const FilePath &anchor) const; - FilePath pathAppended(const QString &str) const; - FilePath stringAppended(const QString &str) const; - FilePath resolvePath(const QString &fileName) const; - FilePath cleanPath() const; - - FilePath canonicalPath() const; - FilePath symLinkTarget() const; - FilePath resolveSymlinks() const; - FilePath withExecutableSuffix() const; - - FilePath operator/(const QString &str) const; - - void clear(); - bool isEmpty() const; - - uint hash(uint seed) const; - - // NOTE: Most FilePath operations on FilePath created from URL currently - // do not work. Among the working are .toVariant() and .toUrl(). - static FilePath fromUrl(const QUrl &url); - QUrl toUrl() const; - - static void setDeviceFileHooks(const DeviceFileHooks &hooks); - - FilePath searchOnDevice(const QList<FilePath> &dirs) const; - Environment deviceEnvironment() const; - - static QString formatFilePaths(const QList<FilePath> &files, const QString &separator); - static void removeDuplicates(QList<FilePath> &files); - static void sort(QList<FilePath> &files); - - static QList<FilePath> filterEntriesHelper(const FilePath &base, - const QStringList &entries, - const QStringList &nameFilters, - QDir::Filters filters, - QDir::SortFlags sort); -private: - friend class ::tst_fileutils; - static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath); - - QString m_scheme; - QString m_host; - QString m_data; -}; - -QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &fn); - -using FilePaths = QList<FilePath>; - class QTCREATOR_UTILS_EXPORT FileUtils { public: #ifdef QT_GUI_LIB @@ -286,6 +126,8 @@ public: static QByteArray fileId(const FilePath &fileName); static FilePath homePath(); static bool renameFile(const FilePath &srcFilePath, const FilePath &tgtFilePath); + + static void setDeviceFileHooks(const DeviceFileHooks &hooks); }; template<typename T> @@ -431,6 +273,8 @@ private: bool m_autoRemove = true; }; +QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &fn); + inline uint qHash(const Utils::FilePath &a, uint seed = 0) { return a.hash(seed); } } // namespace Utils @@ -444,4 +288,3 @@ template<> struct QTCREATOR_UTILS_EXPORT hash<Utils::FilePath> }; } // namespace std -Q_DECLARE_METATYPE(Utils::FilePath) diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index b3d6a7a6dd..4bc4f9e015 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -62,6 +62,7 @@ SOURCES += \ $$PWD/fancylineedit.cpp \ $$PWD/qtcolorbutton.cpp \ $$PWD/savefile.cpp \ + $$PWD/filepath.cpp \ $$PWD/fileutils.cpp \ $$PWD/textfileformat.cpp \ $$PWD/consoleprocess.cpp \ @@ -193,6 +194,7 @@ HEADERS += \ $$PWD/qtcolorbutton.h \ $$PWD/consoleprocess.h \ $$PWD/savefile.h \ + $$PWD/filepath.h \ $$PWD/fileutils.h \ $$PWD/textfileformat.h \ $$PWD/uncommentselection.h \ diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 7a78cb1f41..91f5be066f 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -115,6 +115,8 @@ Project { "fileinprojectfinder.h", "filenamevalidatinglineedit.cpp", "filenamevalidatinglineedit.h", + "filepath.cpp", + "filepath.h", "filesearch.cpp", "filesearch.h", "filesystemwatcher.cpp", diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 41ca11cd90..08260700a1 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -525,7 +525,7 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager return device->systemEnvironment(); }; - FilePath::setDeviceFileHooks(deviceHooks); + FileUtils::setDeviceFileHooks(deviceHooks); DeviceProcessHooks processHooks; diff --git a/src/tools/sdktool/CMakeLists.txt b/src/tools/sdktool/CMakeLists.txt index dc2320a92c..e5197c5952 100644 --- a/src/tools/sdktool/CMakeLists.txt +++ b/src/tools/sdktool/CMakeLists.txt @@ -79,6 +79,7 @@ extend_qtc_executable(sdktool DEFINES QTCREATOR_UTILS_STATIC_LIB SOURCES environment.cpp environment.h + filepath.cpp filepath.h fileutils.cpp fileutils.h hostosinfo.cpp hostosinfo.h namevaluedictionary.cpp namevaluedictionary.h diff --git a/src/tools/sdktool/sdktool.pro b/src/tools/sdktool/sdktool.pro index 0b58755a1e..aa1edfb504 100644 --- a/src/tools/sdktool/sdktool.pro +++ b/src/tools/sdktool/sdktool.pro @@ -32,6 +32,7 @@ SOURCES += \ rmtoolchainoperation.cpp \ settings.cpp \ $$UTILS/environment.cpp \ + $$UTILS/filepath.cpp \ $$UTILS/fileutils.cpp \ $$UTILS/hostosinfo.cpp \ $$UTILS/namevaluedictionary.cpp \ @@ -65,6 +66,7 @@ HEADERS += \ rmtoolchainoperation.h \ settings.h \ $$UTILS/environment.h \ + $$UTILS/filepath.h \ $$UTILS/fileutils.h \ $$UTILS/hostosinfo.h \ $$UTILS/namevaluedictionary.h \ diff --git a/src/tools/sdktool/sdktool.qbs b/src/tools/sdktool/sdktool.qbs index a6fd9ed8b8..bf9b42dfb1 100644 --- a/src/tools/sdktool/sdktool.qbs +++ b/src/tools/sdktool/sdktool.qbs @@ -70,6 +70,7 @@ QtcTool { files: [ "commandline.cpp", "commandline.h", "environment.cpp", "environment.h", + "filepath.cpp", "filepath.h", "fileutils.cpp", "fileutils.h", "hostosinfo.cpp", "hostosinfo.h", "namevaluedictionary.cpp", "namevaluedictionary.h", diff --git a/tests/auto/debugger/gdb.pro b/tests/auto/debugger/gdb.pro index d09e1a7674..80e1322288 100644 --- a/tests/auto/debugger/gdb.pro +++ b/tests/auto/debugger/gdb.pro @@ -19,6 +19,7 @@ HEADERS += \ SOURCES += \ $$UTILSDIR/commandline.cpp \ $$UTILSDIR/environment.cpp \ + $$UTILSDIR/filepath.cpp \ $$UTILSDIR/fileutils.cpp \ $$UTILSDIR/hostosinfo.cpp \ $$UTILSDIR/namevaluedictionary.cpp \ @@ -31,6 +32,7 @@ HEADERS += \ HEADERS += \ $$UTILSDIR/commandline.h \ $$UTILSDIR/environment.h \ + $$UTILSDIR/filepath.h \ $$UTILSDIR/fileutils.h \ $$UTILSDIR/hostosinfo.h \ $$UTILSDIR/namevaluedictionary.h \ |