filepath: Move FileUtils:: function out of FilePath.cpp

Change-Id: If78c349b0145b86a76cd033e4dbc30b237ad917b
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2022-07-26 13:32:07 +02:00
parent 3a59331232
commit 783f0e1205
6 changed files with 243 additions and 235 deletions

View File

@@ -65,12 +65,6 @@ namespace Utils {
static DeviceFileHooks s_deviceHooks;
/*! \class Utils::FileUtils
\brief The FileUtils class contains file and directory related convenience
functions.
*/
static bool removeRecursivelyLocal(const FilePath &filePath, QString *error)
{
@@ -78,9 +72,11 @@ static bool removeRecursivelyLocal(const FilePath &filePath, QString *error)
QFileInfo fileInfo = filePath.toFileInfo();
if (!fileInfo.exists() && !fileInfo.isSymLink())
return true;
QFile::setPermissions(filePath.toString(), fileInfo.permissions() | QFile::WriteUser);
QFile::setPermissions(fileInfo.absoluteFilePath(), fileInfo.permissions() | QFile::WriteUser);
if (fileInfo.isDir()) {
QDir dir(filePath.toString());
QDir dir(fileInfo.absoluteFilePath());
dir.setPath(dir.canonicalPath());
if (dir.isRoot()) {
if (error) {
@@ -122,85 +118,9 @@ static bool removeRecursivelyLocal(const FilePath &filePath, QString *error)
return true;
}
// Cleans part after optional :// scheme separator, similar to QDir::cleanPath()
// - directory separators normalized (that is, platform-native
// separators converted to "/") and redundant ones removed, and "."s and ".."s
// resolved (as far as possible).
// Symbolic links are kept. This function does not return the
// canonical path, but rather the simplest version of the input.
// For example, "./local" becomes "local", "local/../bin" becomes
// "bin" and "/local/usr/../bin" becomes "/local/bin".
// FIXME: This should not use the host-platform dependent QDir::cleanPath()
static QString doCleanPath(const QString &input)
void FilePath::setDeviceFileHooks(const DeviceFileHooks &hooks)
{
const int pos = input.indexOf("://");
if (pos == -1)
return QDir::cleanPath(input);
return input.left(pos + 3) + QDir::cleanPath(input.mid(pos + 3));
}
/*!
Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain
the target directory, which will be created. Example usage:
\code
QString error;
bool ok = Utils::FileUtils::copyRecursively("/foo/bar", "/foo/baz", &error);
if (!ok)
qDebug() << error;
\endcode
This will copy the contents of /foo/bar into to the baz directory under /foo, which will be created in the process.
\note The \a error parameter is optional.
Returns whether the operation succeeded.
*/
bool FileUtils::copyRecursively(const FilePath &srcFilePath, const FilePath &tgtFilePath, QString *error)
{
return copyRecursively(
srcFilePath, tgtFilePath, error, [](const FilePath &src, const FilePath &dest, QString *error) {
if (!src.copyFile(dest)) {
if (error) {
*error = QCoreApplication::translate("Utils::FileUtils",
"Could not copy file \"%1\" to \"%2\".")
.arg(src.toUserOutput(), dest.toUserOutput());
}
return false;
}
return true;
});
}
/*!
Copies a file specified by \a srcFilePath to \a tgtFilePath only if \a srcFilePath is different
(file contents and last modification time).
Returns whether the operation succeeded.
*/
bool FileUtils::copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath)
{
QTC_ASSERT(srcFilePath.exists(), return false);
QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false);
QTC_ASSERT(srcFilePath.host() == tgtFilePath.host(), return false);
if (tgtFilePath.exists()) {
const QDateTime srcModified = srcFilePath.lastModified();
const QDateTime tgtModified = tgtFilePath.lastModified();
if (srcModified == tgtModified) {
const QByteArray srcContents = srcFilePath.fileContents();
const QByteArray tgtContents = srcFilePath.fileContents();
if (srcContents == tgtContents)
return true;
}
tgtFilePath.removeFile();
}
return srcFilePath.copyFile(tgtFilePath);
s_deviceHooks = hooks;
}
/*!
@@ -314,85 +234,6 @@ QString FilePath::shortNativePath() const
return toUserOutput();
}
QString FileUtils::fileSystemFriendlyName(const QString &name)
{
QString result = name;
result.replace(QRegularExpression(QLatin1String("\\W")), QLatin1String("_"));
result.replace(QRegularExpression(QLatin1String("_+")), QLatin1String("_")); // compact _
result.remove(QRegularExpression(QLatin1String("^_*"))); // remove leading _
result.remove(QRegularExpression(QLatin1String("_+$"))); // remove trailing _
if (result.isEmpty())
result = QLatin1String("unknown");
return result;
}
int FileUtils::indexOfQmakeUnfriendly(const QString &name, int startpos)
{
static const QRegularExpression checkRegExp(QLatin1String("[^a-zA-Z0-9_.-]"));
return checkRegExp.match(name, startpos).capturedStart();
}
QString FileUtils::qmakeFriendlyName(const QString &name)
{
QString result = name;
// Remove characters that might trip up a build system (especially qmake):
int pos = indexOfQmakeUnfriendly(result);
while (pos >= 0) {
result[pos] = QLatin1Char('_');
pos = indexOfQmakeUnfriendly(result, pos);
}
return fileSystemFriendlyName(result);
}
bool FileUtils::makeWritable(const FilePath &path)
{
return path.setPermissions(path.permissions() | QFile::WriteUser);
}
// makes sure that capitalization of directories is canonical on Windows and OS X.
// This mimics the logic in QDeclarative_isFileCaseCorrect
QString FileUtils::normalizedPathName(const QString &name)
{
#ifdef Q_OS_WIN
const QString nativeSeparatorName(QDir::toNativeSeparators(name));
const auto nameC = reinterpret_cast<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
{
@@ -422,20 +263,6 @@ FilePath FilePath::cleanPath() const
return result;
}
FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &filePath)
{
FilePath newCommonPath = oldCommonPath;
while (!newCommonPath.isEmpty() && !filePath.isChildOf(newCommonPath))
newCommonPath = newCommonPath.parentDir();
return newCommonPath.canonicalPath();
}
FilePath FileUtils::homePath()
{
return FilePath::fromString(doCleanPath(QDir::homePath()));
}
/*! \class Utils::FilePath
\brief The FilePath class is an abstraction for handles to objects
@@ -597,11 +424,6 @@ QUrl FilePath::toUrl() const
return url;
}
void FileUtils::setDeviceFileHooks(const DeviceFileHooks &hooks)
{
s_deviceHooks = hooks;
}
/// \returns a QString to display to the user, including the device prefix
/// Converts the separators to the native format of the system
/// this path belongs to.
@@ -1175,13 +997,11 @@ void FilePath::setFromString(const QString &filename)
}
m_data = "/";
} else {
m_scheme.clear();
m_host.clear();
m_data = filename;
return;
}
m_scheme.clear();
m_host.clear();
m_data = filename;
return;
}
}

View File

@@ -47,6 +47,7 @@ class tst_fileutils; // This becomes a friend of Utils::FilePath for testing pri
namespace Utils {
class DeviceFileHooks;
class Environment;
class EnvironmentChange;
@@ -222,6 +223,8 @@ public:
[[nodiscard]] static QString specialPath(SpecialPathComponent component);
[[nodiscard]] static FilePath specialFilePath(SpecialPathComponent component);
static void setDeviceFileHooks(const DeviceFileHooks &hooks);
private:
friend class ::tst_fileutils;
static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath);
@@ -238,6 +241,47 @@ inline size_t qHash(const Utils::FilePath &a, uint seed = 0)
return a.hash(seed);
}
class DeviceFileHooks
{
public:
std::function<bool(const FilePath &)> isExecutableFile;
std::function<bool(const FilePath &)> isReadableFile;
std::function<bool(const FilePath &)> isReadableDir;
std::function<bool(const FilePath &)> isWritableDir;
std::function<bool(const FilePath &)> isWritableFile;
std::function<bool(const FilePath &)> isFile;
std::function<bool(const FilePath &)> isDir;
std::function<bool(const FilePath &)> ensureWritableDir;
std::function<bool(const FilePath &)> ensureExistingFile;
std::function<bool(const FilePath &)> createDir;
std::function<bool(const FilePath &)> exists;
std::function<bool(const FilePath &)> removeFile;
std::function<bool(const FilePath &)> removeRecursively;
std::function<bool(const FilePath &, const FilePath &)> copyFile;
std::function<bool(const FilePath &, const FilePath &)> renameFile;
std::function<FilePath(const FilePath &, const FilePaths &)> searchInPath;
std::function<FilePath(const FilePath &)> symLinkTarget;
std::function<QString(const FilePath &)> mapToDevicePath;
std::function<void(const FilePath &,
const std::function<bool(const FilePath &)> &, // Abort on 'false' return.
const FileFilter &)> iterateDirectory;
std::function<QByteArray(const FilePath &, qint64, qint64)> fileContents;
std::function<bool(const FilePath &, const QByteArray &)> writeFileContents;
std::function<QDateTime(const FilePath &)> lastModified;
std::function<QFile::Permissions(const FilePath &)> permissions;
std::function<bool(const FilePath &, QFile::Permissions)> setPermissions;
std::function<OsType(const FilePath &)> osType;
std::function<Environment(const FilePath &)> environment;
std::function<qint64(const FilePath &)> fileSize;
std::function<qint64(const FilePath &)> bytesAvailable;
std::function<QString(const FilePath &)> deviceDisplayName;
template <class ...Args> using Continuation = std::function<void(Args...)>;
std::function<void(const Continuation<bool> &, const FilePath &, const FilePath &)> asyncCopyFile;
std::function<void(const Continuation<const QByteArray &> &, const FilePath &, qint64, qint64)> asyncFileContents;
std::function<void(const Continuation<bool> &, const FilePath &, const QByteArray &)> asyncWriteFileContents;
};
} // namespace Utils
QT_BEGIN_NAMESPACE
@@ -254,4 +298,5 @@ struct QTCREATOR_UTILS_EXPORT hash<Utils::FilePath>
using result_type = size_t;
result_type operator()(const argument_type &fn) const;
};
} // namespace std

View File

@@ -28,6 +28,7 @@
#include "algorithm.h"
#include "qtcassert.h"
#include "hostosinfo.h"
#include "fsengine/fileiconprovider.h"
#include "fsengine/fsengine.h"
@@ -35,6 +36,7 @@
#include <QDataStream>
#include <QDebug>
#include <QOperatingSystemVersion>
#include <QRegularExpression>
#include <QTemporaryFile>
#include <QTextStream>
#include <QXmlStreamWriter>
@@ -285,6 +287,13 @@ TempFileSaver::~TempFileSaver()
QFile::remove(m_filePath.toString());
}
/*! \class Utils::FileUtils
\brief The FileUtils class contains file and directory related convenience
functions.
*/
#ifdef QT_GUI_LIB
FileUtils::CopyAskingForOverwrite::CopyAskingForOverwrite(QWidget *dialogParent, const std::function<void (FilePath)> &postOperation)
: m_parent(dialogParent)
@@ -629,4 +638,178 @@ void FileUtils::iterateLsOutput(const FilePath &base,
#endif // QT_WIDGETS_LIB
/*!
Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain
the target directory, which will be created. Example usage:
\code
QString error;
bool ok = Utils::FileUtils::copyRecursively("/foo/bar", "/foo/baz", &error);
if (!ok)
qDebug() << error;
\endcode
This will copy the contents of /foo/bar into to the baz directory under /foo, which will be created in the process.
\note The \a error parameter is optional.
Returns whether the operation succeeded.
*/
bool FileUtils::copyRecursively(const FilePath &srcFilePath, const FilePath &tgtFilePath, QString *error)
{
return copyRecursively(
srcFilePath, tgtFilePath, error, [](const FilePath &src, const FilePath &dest, QString *error) {
if (!src.copyFile(dest)) {
if (error) {
*error = QCoreApplication::translate("Utils::FileUtils",
"Could not copy file \"%1\" to \"%2\".")
.arg(src.toUserOutput(), dest.toUserOutput());
}
return false;
}
return true;
});
}
/*!
Copies a file specified by \a srcFilePath to \a tgtFilePath only if \a srcFilePath is different
(file contents and last modification time).
Returns whether the operation succeeded.
*/
bool FileUtils::copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath)
{
QTC_ASSERT(srcFilePath.exists(), return false);
QTC_ASSERT(srcFilePath.scheme() == tgtFilePath.scheme(), return false);
QTC_ASSERT(srcFilePath.host() == tgtFilePath.host(), return false);
if (tgtFilePath.exists()) {
const QDateTime srcModified = srcFilePath.lastModified();
const QDateTime tgtModified = tgtFilePath.lastModified();
if (srcModified == tgtModified) {
const QByteArray srcContents = srcFilePath.fileContents();
const QByteArray tgtContents = srcFilePath.fileContents();
if (srcContents == tgtContents)
return true;
}
tgtFilePath.removeFile();
}
return srcFilePath.copyFile(tgtFilePath);
}
QString FileUtils::fileSystemFriendlyName(const QString &name)
{
QString result = name;
result.replace(QRegularExpression(QLatin1String("\\W")), QLatin1String("_"));
result.replace(QRegularExpression(QLatin1String("_+")), QLatin1String("_")); // compact _
result.remove(QRegularExpression(QLatin1String("^_*"))); // remove leading _
result.remove(QRegularExpression(QLatin1String("_+$"))); // remove trailing _
if (result.isEmpty())
result = QLatin1String("unknown");
return result;
}
int FileUtils::indexOfQmakeUnfriendly(const QString &name, int startpos)
{
static const QRegularExpression checkRegExp(QLatin1String("[^a-zA-Z0-9_.-]"));
return checkRegExp.match(name, startpos).capturedStart();
}
QString FileUtils::qmakeFriendlyName(const QString &name)
{
QString result = name;
// Remove characters that might trip up a build system (especially qmake):
int pos = indexOfQmakeUnfriendly(result);
while (pos >= 0) {
result[pos] = QLatin1Char('_');
pos = indexOfQmakeUnfriendly(result, pos);
}
return fileSystemFriendlyName(result);
}
bool FileUtils::makeWritable(const FilePath &path)
{
return path.setPermissions(path.permissions() | QFile::WriteUser);
}
// makes sure that capitalization of directories is canonical on Windows and macOS.
// This mimics the logic in QDeclarative_isFileCaseCorrect
QString FileUtils::normalizedPathName(const QString &name)
{
#ifdef Q_OS_WIN
const QString nativeSeparatorName(QDir::toNativeSeparators(name));
const auto nameC = reinterpret_cast<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_MACOS)
return Internal::normalizePathName(name);
#else // do not try to handle case-insensitive file systems on Linux
return name;
#endif
}
bool isRelativePathHelper(const QString &path, OsType osType)
{
if (path.startsWith('/'))
return false;
if (osType == OsType::OsTypeWindows) {
if (path.startsWith('\\'))
return false;
// Unlike QFileInfo, this won't accept a relative path with a drive letter.
// Such paths result in a royal mess anyway ...
if (path.length() >= 3 && path.at(1) == ':' && path.at(0).isLetter()
&& (path.at(2) == '/' || path.at(2) == '\\'))
return false;
}
return true;
}
bool FileUtils::isRelativePath(const QString &path)
{
return isRelativePathHelper(path, HostOsInfo::hostOs());
}
// Cleans part after optional :// scheme separator, similar to QDir::cleanPath()
// - directory separators normalized (that is, platform-native
// separators converted to "/") and redundant ones removed, and "."s and ".."s
// resolved (as far as possible).
// Symbolic links are kept. This function does not return the
// canonical path, but rather the simplest version of the input.
// For example, "./local" becomes "local", "local/../bin" becomes
// "bin" and "/local/usr/../bin" becomes "/local/bin".
// FIXME: This should not use the host-platform dependent QDir::cleanPath()
QString doCleanPath(const QString &input)
{
const int pos = input.indexOf("://");
if (pos == -1)
return QDir::cleanPath(input);
return input.left(pos + 3) + QDir::cleanPath(input.mid(pos + 3));
}
FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &filePath)
{
FilePath newCommonPath = oldCommonPath;
while (!newCommonPath.isEmpty() && !filePath.isChildOf(newCommonPath))
newCommonPath = newCommonPath.parentDir();
return newCommonPath.canonicalPath();
}
FilePath FileUtils::homePath()
{
return FilePath::fromString(doCleanPath(QDir::homePath()));
}
} // namespace Utils

View File

@@ -53,47 +53,6 @@ QT_END_NAMESPACE
namespace Utils {
class DeviceFileHooks
{
public:
std::function<bool(const FilePath &)> isExecutableFile;
std::function<bool(const FilePath &)> isReadableFile;
std::function<bool(const FilePath &)> isReadableDir;
std::function<bool(const FilePath &)> isWritableDir;
std::function<bool(const FilePath &)> isWritableFile;
std::function<bool(const FilePath &)> isFile;
std::function<bool(const FilePath &)> isDir;
std::function<bool(const FilePath &)> ensureWritableDir;
std::function<bool(const FilePath &)> ensureExistingFile;
std::function<bool(const FilePath &)> createDir;
std::function<bool(const FilePath &)> exists;
std::function<bool(const FilePath &)> removeFile;
std::function<bool(const FilePath &)> removeRecursively;
std::function<bool(const FilePath &, const FilePath &)> copyFile;
std::function<bool(const FilePath &, const FilePath &)> renameFile;
std::function<FilePath(const FilePath &, const FilePaths &)> searchInPath;
std::function<FilePath(const FilePath &)> symLinkTarget;
std::function<QString(const FilePath &)> mapToDevicePath;
std::function<void(const FilePath &,
const std::function<bool(const FilePath &)> &, // Abort on 'false' return.
const FileFilter &)> iterateDirectory;
std::function<QByteArray(const FilePath &, qint64, qint64)> fileContents;
std::function<bool(const FilePath &, const QByteArray &)> writeFileContents;
std::function<QDateTime(const FilePath &)> lastModified;
std::function<QFile::Permissions(const FilePath &)> permissions;
std::function<bool(const FilePath &, QFile::Permissions)> setPermissions;
std::function<OsType(const FilePath &)> osType;
std::function<Environment(const FilePath &)> environment;
std::function<qint64(const FilePath &)> fileSize;
std::function<qint64(const FilePath &)> bytesAvailable;
std::function<QString(const FilePath &)> deviceDisplayName;
template <class ...Args> using Continuation = std::function<void(Args...)>;
std::function<void(const Continuation<bool> &, const FilePath &, const FilePath &)> asyncCopyFile;
std::function<void(const Continuation<const QByteArray &> &, const FilePath &, qint64, qint64)> asyncFileContents;
std::function<void(const Continuation<bool> &, const FilePath &, const QByteArray &)> asyncWriteFileContents;
};
class QTCREATOR_UTILS_EXPORT FileUtils
{
public:
@@ -138,8 +97,6 @@ public:
static QByteArray fileId(const FilePath &fileName);
static FilePath homePath();
static void setDeviceFileHooks(const DeviceFileHooks &hooks);
static void iterateLsOutput(const FilePath &base,
const QStringList &entries,
const FileFilter &filter,
@@ -328,5 +285,8 @@ private:
QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &fn);
bool isRelativePathHelper(const QString &path, OsType osType);
QString doCleanPath(const QString &input);
} // namespace Utils

View File

@@ -610,7 +610,7 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager
return device->displayName();
};
FileUtils::setDeviceFileHooks(deviceHooks);
FilePath::setDeviceFileHooks(deviceHooks);
DeviceProcessHooks processHooks;

View File

@@ -175,7 +175,7 @@ void tst_fsengine::initTestCase()
deviceHooks.mapToDevicePath = [](const FilePath &filePath) { return filePath.path(); };
FileUtils::setDeviceFileHooks(deviceHooks);
FilePath::setDeviceFileHooks(deviceHooks);
FSEngine::addDevice(FilePath::fromString("device://test"));