summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhjk <hjk@qt.io>2021-07-16 11:16:45 +0200
committerhjk <hjk@qt.io>2021-07-22 11:52:58 +0000
commitfd677101a9f7428ed6e86166e670397921b00989 (patch)
tree34edd24edc9fbdb6a631e5a59af4bebb23d0969d
parent805d19d1edf5d76f97d436161be79f2e39678bda (diff)
downloadqt-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.txt1
-rw-r--r--src/libs/utils/filepath.cpp1329
-rw-r--r--src/libs/utils/filepath.h198
-rw-r--r--src/libs/utils/fileutils.cpp1409
-rw-r--r--src/libs/utils/fileutils.h167
-rw-r--r--src/libs/utils/utils-lib.pri2
-rw-r--r--src/libs/utils/utils.qbs2
-rw-r--r--src/plugins/projectexplorer/devicesupport/devicemanager.cpp2
-rw-r--r--src/tools/sdktool/CMakeLists.txt1
-rw-r--r--src/tools/sdktool/sdktool.pro2
-rw-r--r--src/tools/sdktool/sdktool.qbs1
-rw-r--r--tests/auto/debugger/gdb.pro2
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 \