2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2021 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
2021-07-16 11:16:45 +02:00
|
|
|
|
|
|
|
|
#include "filepath.h"
|
|
|
|
|
|
|
|
|
|
#include "algorithm.h"
|
2022-10-10 17:32:56 +02:00
|
|
|
#include "devicefileaccess.h"
|
2021-07-16 11:16:45 +02:00
|
|
|
#include "environment.h"
|
|
|
|
|
#include "fileutils.h"
|
|
|
|
|
#include "hostosinfo.h"
|
|
|
|
|
#include "qtcassert.h"
|
|
|
|
|
|
2021-09-14 14:20:50 +02:00
|
|
|
#include <QtGlobal>
|
2021-07-16 11:16:45 +02:00
|
|
|
#include <QDateTime>
|
|
|
|
|
#include <QDebug>
|
2021-12-14 18:04:41 +01:00
|
|
|
#include <QDirIterator>
|
2021-07-16 11:16:45 +02:00
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QRegularExpression>
|
|
|
|
|
#include <QUrl>
|
2022-05-31 11:16:44 +02:00
|
|
|
#include <QStringView>
|
2021-07-16 11:16:45 +02:00
|
|
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
#ifdef QTCREATOR_PCH_H
|
|
|
|
|
#define CALLBACK WINAPI
|
|
|
|
|
#endif
|
|
|
|
|
#include <qt_windows.h>
|
|
|
|
|
#include <shlobj.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
namespace Utils {
|
|
|
|
|
|
|
|
|
|
static DeviceFileHooks s_deviceHooks;
|
2022-09-13 08:58:31 +02:00
|
|
|
inline bool isWindowsDriveLetter(QChar ch);
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
|
|
|
|
|
/*! \class Utils::FilePath
|
|
|
|
|
|
2022-07-26 10:55:10 +02:00
|
|
|
\brief The FilePath class is an abstraction for handles to objects
|
|
|
|
|
in a (possibly remote) file system, similar to a URL or, in the local
|
|
|
|
|
case, a path to a file or directory.
|
2021-07-16 11:16:45 +02:00
|
|
|
|
2022-07-26 10:55:10 +02:00
|
|
|
Ideally, all of \QC code should use FilePath for this purpose,
|
|
|
|
|
but for historic reasons there are still large parts using QString.
|
|
|
|
|
|
|
|
|
|
FilePaths are internally stored as triple of strings, with one
|
|
|
|
|
part ("scheme") identifying an access method, a second part ("host")
|
|
|
|
|
a file system (e.g. a host) and a third part ("path") identifying
|
|
|
|
|
a (potential) object on the systems.
|
|
|
|
|
|
|
|
|
|
FilePath follows the Unix paradigm of "everything is a file":
|
|
|
|
|
There is no conceptional difference between FilePaths referring
|
|
|
|
|
to plain files or directories.
|
|
|
|
|
|
|
|
|
|
A FilePath is implicitly associated with an operating system via its
|
|
|
|
|
host part. The path part of a FilePath is internally stored
|
|
|
|
|
with forward slashes, independent of the associated OS.
|
|
|
|
|
|
|
|
|
|
The path parts of FilePaths associated with Windows (and macOS,
|
|
|
|
|
unless selected otherwise in the settings) are compared case-insensitively
|
|
|
|
|
to each other.
|
|
|
|
|
Note that comparisons for equivalence generally need out-of-band
|
|
|
|
|
knowledge, as there may be multiple FilePath representations for
|
|
|
|
|
the same file (e.g. different access methods may lead to the same
|
|
|
|
|
file).
|
|
|
|
|
|
|
|
|
|
There are several conversions between FilePath and other string-like
|
|
|
|
|
representations:
|
|
|
|
|
|
|
|
|
|
\list
|
|
|
|
|
|
|
|
|
|
\li FilePath::fromUserInput()
|
|
|
|
|
|
|
|
|
|
Convert string-like data from sources originating outside of
|
|
|
|
|
\QC, e.g. from human input in GUI controls, from environment
|
|
|
|
|
variables and from command-line parameters to \QC.
|
|
|
|
|
|
|
|
|
|
The input can contain both slashes and backslashes and will
|
|
|
|
|
be parsed and normalized.
|
|
|
|
|
|
|
|
|
|
\li FilePath::nativePath()
|
|
|
|
|
|
|
|
|
|
Converts the FilePath to the slash convention of the associated
|
|
|
|
|
OS and drops the scheme and host parts.
|
|
|
|
|
|
|
|
|
|
This is useful to interact with the facilities of the associated
|
|
|
|
|
OS, e.g. when passing this FilePath as an argument to a command
|
|
|
|
|
executed on the associated OS.
|
|
|
|
|
|
|
|
|
|
\note The FilePath passed as executable to a CommandLine is typically
|
|
|
|
|
not touched by user code. QtcProcess will use it to determine
|
|
|
|
|
the remote system and apply the necessary conversions internally.
|
|
|
|
|
|
|
|
|
|
\li FilePath::toUserOutput()
|
|
|
|
|
|
|
|
|
|
Converts the FilePath to the slash convention of the associated
|
|
|
|
|
OS and retains the scheme and host parts.
|
|
|
|
|
|
|
|
|
|
This is rarely useful for remote paths as there is practically
|
|
|
|
|
no consumer of this style.
|
|
|
|
|
|
|
|
|
|
\li FilePath::displayName()
|
|
|
|
|
|
|
|
|
|
Converts the FilePath to the slash convention of the associated
|
|
|
|
|
OS and adds the scheme and host as a " on <device>" suffix.
|
|
|
|
|
|
|
|
|
|
This is useful for static user-facing output in he GUI
|
|
|
|
|
|
|
|
|
|
\li FilePath::fromVariant(), FilePath::toVariant()
|
|
|
|
|
|
|
|
|
|
These are used to interface QVariant-based API, e.g.
|
|
|
|
|
settings or item model (internal) data.
|
|
|
|
|
|
|
|
|
|
\li FilePath::fromString(), FilePath::toString()
|
|
|
|
|
|
|
|
|
|
These are used for internal interfaces to code areas that
|
|
|
|
|
still use QString based file paths.
|
|
|
|
|
|
|
|
|
|
\endlist
|
|
|
|
|
|
|
|
|
|
Conversion of string-like data should always happen at the outer boundary
|
|
|
|
|
of \QC code, using \c fromUserInput() for in-bound communication,
|
|
|
|
|
and depending on the medium \c nativePath() or \c displayName() for out-bound
|
|
|
|
|
communication.
|
|
|
|
|
|
|
|
|
|
Communication with QVariant based Qt API should use \c fromVariant() and
|
|
|
|
|
\c toVariant().
|
|
|
|
|
|
|
|
|
|
Uses of \c fromString() and \c toString() should be phased out by transforming
|
|
|
|
|
code from QString based file path to FilePath. An exception here are
|
|
|
|
|
fragments of paths of a FilePath that are later used with \c pathAppended()
|
|
|
|
|
or similar which should be kept as QString.
|
2022-09-13 12:02:15 +02:00
|
|
|
|
|
|
|
|
UNC paths will retain their "//" begin, and are recognizable by this.
|
2021-07-16 11:16:45 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
{
|
2022-10-14 09:46:32 +02:00
|
|
|
return QFileInfo(toFSPathString());
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FilePath::fromUrl(const QUrl &url)
|
|
|
|
|
{
|
2022-08-04 15:00:24 +02:00
|
|
|
return FilePath::fromParts(url.scheme(), url.host(), url.path());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FilePath::fromParts(const QStringView scheme, const QStringView host, const QStringView path)
|
|
|
|
|
{
|
|
|
|
|
FilePath result;
|
|
|
|
|
result.setParts(scheme, host, path);
|
|
|
|
|
return result;
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-28 15:59:52 +02:00
|
|
|
FilePath FilePath::currentWorkingPath()
|
|
|
|
|
{
|
|
|
|
|
return FilePath::fromString(QDir::currentPath());
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-16 11:14:59 +02:00
|
|
|
bool FilePath::isRootPath() const
|
2022-07-28 15:59:52 +02:00
|
|
|
{
|
2022-09-16 11:14:59 +02:00
|
|
|
// FIXME: Make host-independent
|
|
|
|
|
return operator==(FilePath::fromString(QDir::rootPath()));
|
2022-07-28 15:59:52 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-02 16:47:54 +02:00
|
|
|
QString FilePath::encodedHost() const
|
2022-02-24 16:00:58 +01:00
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
QString result = host().toString();
|
|
|
|
|
result.replace('%', "%25");
|
|
|
|
|
result.replace('/', "%2f");
|
|
|
|
|
return result;
|
2022-02-24 16:00:58 +01:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
/// \returns a QString for passing on to QString based APIs
|
|
|
|
|
QString FilePath::toString() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
if (!needsDevice())
|
|
|
|
|
return path();
|
2022-05-31 11:16:44 +02:00
|
|
|
|
2022-08-11 16:32:19 +02:00
|
|
|
if (isRelativePath())
|
2022-09-21 12:59:02 +02:00
|
|
|
return scheme() + "://" + encodedHost() + "/./" + path();
|
|
|
|
|
return scheme() + "://" + encodedHost() + path();
|
2022-05-31 11:16:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FilePath::toFSPathString() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
if (scheme().isEmpty())
|
|
|
|
|
return path();
|
2022-09-16 10:00:09 +02:00
|
|
|
|
|
|
|
|
if (isRelativePath())
|
2022-09-21 12:59:02 +02:00
|
|
|
return specialPath(SpecialPathComponent::RootPath) + "/" + scheme() + "/" + encodedHost() + "/./" + path();
|
|
|
|
|
return specialPath(SpecialPathComponent::RootPath) + "/" + scheme() + "/" + encodedHost() + path();
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QUrl FilePath::toUrl() const
|
|
|
|
|
{
|
|
|
|
|
QUrl url;
|
2022-09-21 12:59:02 +02:00
|
|
|
url.setScheme(scheme().toString());
|
|
|
|
|
url.setHost(host().toString());
|
|
|
|
|
url.setPath(path());
|
2021-07-16 11:16:45 +02:00
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-09 18:20:14 +01:00
|
|
|
/// \returns a QString to display to the user, including the device prefix
|
2021-09-28 15:11:26 +02:00
|
|
|
/// Converts the separators to the native format of the system
|
|
|
|
|
/// this path belongs to.
|
2021-07-16 11:16:45 +02:00
|
|
|
QString FilePath::toUserOutput() const
|
|
|
|
|
{
|
2022-07-27 11:38:01 +02:00
|
|
|
QString tmp = toString();
|
2022-09-21 12:59:02 +02:00
|
|
|
if (needsDevice())
|
|
|
|
|
return tmp;
|
|
|
|
|
|
2021-09-28 15:11:26 +02:00
|
|
|
if (osType() == OsTypeWindows)
|
2022-07-27 11:38:01 +02:00
|
|
|
tmp.replace('/', '\\');
|
|
|
|
|
return tmp;
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-09 18:20:14 +01:00
|
|
|
/// \returns a QString to pass to target system native commands, without the device prefix.
|
|
|
|
|
/// Converts the separators to the native format of the system
|
|
|
|
|
/// this path belongs to.
|
2021-11-10 16:19:25 +01:00
|
|
|
QString FilePath::nativePath() const
|
2021-11-09 18:20:14 +01:00
|
|
|
{
|
2022-07-27 11:38:01 +02:00
|
|
|
QString data = path();
|
2021-11-09 18:20:14 +01:00
|
|
|
if (osType() == OsTypeWindows)
|
|
|
|
|
data.replace('/', '\\');
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
QString FilePath::fileName() const
|
|
|
|
|
{
|
2022-08-11 16:32:19 +02:00
|
|
|
// FIXME: Performance
|
2022-09-13 08:58:31 +02:00
|
|
|
QString fp = path();
|
|
|
|
|
return fp.mid(fp.lastIndexOf('/') + 1);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FilePath::fileNameWithPathComponents(int pathComponents) const
|
|
|
|
|
{
|
2022-07-27 11:38:01 +02:00
|
|
|
QString fullPath = path();
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
if (pathComponents < 0)
|
2022-07-27 11:38:01 +02:00
|
|
|
return fullPath;
|
2021-07-16 11:16:45 +02:00
|
|
|
const QChar slash = QLatin1Char('/');
|
2022-07-27 11:38:01 +02:00
|
|
|
int i = fullPath.lastIndexOf(slash);
|
2021-07-16 11:16:45 +02:00
|
|
|
if (pathComponents == 0 || i == -1)
|
2022-07-27 11:38:01 +02:00
|
|
|
return fullPath.mid(i + 1);
|
2021-07-16 11:16:45 +02:00
|
|
|
int component = i + 1;
|
|
|
|
|
// skip adjacent slashes
|
2022-07-27 11:38:01 +02:00
|
|
|
while (i > 0 && fullPath.at(--i) == slash)
|
2021-07-16 11:16:45 +02:00
|
|
|
;
|
|
|
|
|
while (i >= 0 && --pathComponents >= 0) {
|
2022-07-27 11:38:01 +02:00
|
|
|
i = fullPath.lastIndexOf(slash, i);
|
2021-07-16 11:16:45 +02:00
|
|
|
component = i + 1;
|
2022-07-27 11:38:01 +02:00
|
|
|
while (i > 0 && fullPath.at(--i) == slash)
|
2021-07-16 11:16:45 +02:00
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
if (i > 0 && fullPath.lastIndexOf(slash, i) != -1)
|
|
|
|
|
return fullPath.mid(component);
|
2022-06-10 13:26:46 +02:00
|
|
|
|
|
|
|
|
// If there are no more slashes before the found one, return the entire string
|
2022-09-23 11:21:13 +02:00
|
|
|
return toString();
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// \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
|
2021-09-06 14:44:02 +02:00
|
|
|
/// (but not including) the last '.' character. In case of ".ui.qml"
|
|
|
|
|
/// it will be treated as one suffix.
|
2021-07-16 11:16:45 +02:00
|
|
|
|
|
|
|
|
QString FilePath::completeBaseName() const
|
|
|
|
|
{
|
|
|
|
|
const QString &name = fileName();
|
2021-09-06 14:44:02 +02:00
|
|
|
if (name.endsWith(".ui.qml"))
|
|
|
|
|
return name.left(name.length() - QString(".ui.qml").length());
|
2021-07-16 11:16:45 +02:00
|
|
|
return name.left(name.lastIndexOf('.'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// \returns the suffix (extension) of the file.
|
|
|
|
|
///
|
|
|
|
|
/// The suffix consists of all characters in the file after
|
2021-09-06 14:44:02 +02:00
|
|
|
/// (but not including) the last '.'. In case of ".ui.qml" it will
|
|
|
|
|
/// be treated as one suffix.
|
2021-07-16 11:16:45 +02:00
|
|
|
|
|
|
|
|
QString FilePath::suffix() const
|
|
|
|
|
{
|
|
|
|
|
const QString &name = fileName();
|
2021-09-06 14:44:02 +02:00
|
|
|
if (name.endsWith(".ui.qml"))
|
|
|
|
|
return "ui.qml";
|
2021-07-16 11:16:45 +02:00
|
|
|
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 {};
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-04 13:35:42 +02:00
|
|
|
QStringView FilePath::scheme() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
return QStringView{m_data}.mid(m_pathLen, m_schemeLen);
|
2022-08-04 13:35:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringView FilePath::host() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
return QStringView{m_data}.mid(m_pathLen + m_schemeLen, m_hostLen);
|
2022-08-04 13:35:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FilePath::path() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
if (m_data.startsWith("/./"))
|
|
|
|
|
return m_data.mid(3, m_pathLen - 3);
|
|
|
|
|
return m_data.left(m_pathLen);
|
2022-08-11 16:32:19 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-04 15:00:24 +02:00
|
|
|
void FilePath::setParts(const QStringView scheme, const QStringView host, const QStringView path)
|
2022-08-04 13:35:42 +02:00
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
QTC_CHECK(!scheme.contains('/'));
|
|
|
|
|
|
|
|
|
|
m_data = path.toString() + scheme.toString() + host.toString();
|
|
|
|
|
m_schemeLen = scheme.size();
|
|
|
|
|
m_hostLen = host.size();
|
|
|
|
|
m_pathLen = path.size();
|
2022-08-04 13:35:42 +02:00
|
|
|
}
|
2021-07-16 11:16:45 +02:00
|
|
|
|
|
|
|
|
/// \returns a bool indicating whether a file with this
|
|
|
|
|
/// FilePath exists.
|
|
|
|
|
bool FilePath::exists() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->exists(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// \returns a bool indicating whether a path is writable.
|
|
|
|
|
bool FilePath::isWritableDir() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->isWritableDirectory(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::isWritableFile() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->isWritableFile(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::ensureWritableDir() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->ensureWritableDirectory(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::ensureExistingFile() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->ensureExistingFile(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::isExecutableFile() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->isExecutableFile(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::isReadableFile() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->isReadableFile(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::isReadableDir() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->isReadableDirectory(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::isFile() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->isFile(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::isDir() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->isDirectory(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-14 10:11:13 +02:00
|
|
|
bool FilePath::isSymLink() const
|
|
|
|
|
{
|
|
|
|
|
return fileAccess()->isSymLink(*this);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
bool FilePath::createDir() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->createDirectory(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-01-21 12:22:54 +01:00
|
|
|
FilePaths FilePath::dirEntries(const FileFilter &filter, QDir::SortFlags sort) const
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
2021-12-14 18:04:41 +01:00
|
|
|
FilePaths result;
|
|
|
|
|
|
2022-10-10 17:32:56 +02:00
|
|
|
const auto callBack = [&result](const FilePath &path) { result.append(path); return true; };
|
|
|
|
|
iterateDirectory(callBack, filter);
|
2021-07-16 11:16:45 +02:00
|
|
|
|
2021-12-14 18:04:41 +01:00
|
|
|
// FIXME: Not all flags supported here.
|
2022-01-27 09:25:03 +01:00
|
|
|
const QDir::SortFlags sortBy = (sort & QDir::SortByMask);
|
|
|
|
|
if (sortBy == QDir::Name) {
|
2021-12-14 18:04:41 +01:00
|
|
|
Utils::sort(result);
|
2022-01-27 09:25:03 +01:00
|
|
|
} else if (sortBy == QDir::Time) {
|
|
|
|
|
Utils::sort(result, [](const FilePath &path1, const FilePath &path2) {
|
|
|
|
|
return path1.lastModified() < path2.lastModified();
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-07-16 11:16:45 +02:00
|
|
|
|
2021-12-14 18:04:41 +01:00
|
|
|
if (sort & QDir::Reversed)
|
|
|
|
|
std::reverse(result.begin(), result.end());
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePaths FilePath::dirEntries(QDir::Filters filters) const
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
2022-01-21 12:22:54 +01:00
|
|
|
return dirEntries(FileFilter({}, filters));
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-12-14 18:04:41 +01:00
|
|
|
// This runs \a callBack on each directory entry matching all \a filters and
|
|
|
|
|
// either of the specified \a nameFilters.
|
|
|
|
|
// An empty \nameFilters list matches every name.
|
|
|
|
|
|
2022-10-05 15:42:14 +02:00
|
|
|
void FilePath::iterateDirectory(const IterateDirCallback &callBack, const FileFilter &filter) const
|
2021-09-14 14:20:50 +02:00
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
fileAccess()->iterateDirectory(*this, callBack, filter);
|
2022-10-05 15:42:14 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-01 15:43:51 +02:00
|
|
|
void FilePath::iterateDirectories(const FilePaths &dirs,
|
2022-10-05 15:42:14 +02:00
|
|
|
const IterateDirCallback &callBack,
|
2022-07-01 15:43:51 +02:00
|
|
|
const FileFilter &filter)
|
|
|
|
|
{
|
|
|
|
|
for (const FilePath &dir : dirs)
|
|
|
|
|
dir.iterateDirectory(callBack, filter);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-09 13:48:08 +02:00
|
|
|
std::optional<QByteArray> FilePath::fileContents(qint64 maxSize, qint64 offset) const
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->fileContents(*this, maxSize, offset);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-14 09:55:30 +02:00
|
|
|
bool FilePath::ensureReachable(const FilePath &other) const
|
|
|
|
|
{
|
|
|
|
|
if (needsDevice()) {
|
|
|
|
|
QTC_ASSERT(s_deviceHooks.ensureReachable, return false);
|
|
|
|
|
return s_deviceHooks.ensureReachable(*this, other);
|
|
|
|
|
} else if (!other.needsDevice()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-10 17:32:56 +02:00
|
|
|
void FilePath::asyncFileContents(
|
|
|
|
|
const Continuation<const std::optional<QByteArray> &> &cont,
|
|
|
|
|
qint64 maxSize,
|
|
|
|
|
qint64 offset) const
|
2021-08-30 15:42:21 +02:00
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->asyncFileContents(*this, cont, maxSize, offset);
|
2021-08-30 15:42:21 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-10 17:11:51 +02:00
|
|
|
bool FilePath::writeFileContents(const QByteArray &data, qint64 offset) const
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->writeFileContents(*this, data, offset);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-05 15:42:14 +02:00
|
|
|
FilePathInfo FilePath::filePathInfo() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->filePathInfo(*this);
|
2022-10-05 15:42:14 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-10 17:32:56 +02:00
|
|
|
void FilePath::asyncWriteFileContents(
|
|
|
|
|
const Continuation<bool> &cont,
|
|
|
|
|
const QByteArray &data,
|
|
|
|
|
qint64 offset) const
|
2021-08-30 15:42:21 +02:00
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->asyncWriteFileContents(*this, cont, data, offset);
|
2021-08-30 15:42:21 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
bool FilePath::needsDevice() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
return m_schemeLen != 0;
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-16 10:26:41 +02:00
|
|
|
bool FilePath::isSameDevice(const FilePath &other) const
|
|
|
|
|
{
|
|
|
|
|
if (needsDevice() != other.needsDevice())
|
|
|
|
|
return false;
|
|
|
|
|
if (!needsDevice() && !other.needsDevice())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
QTC_ASSERT(s_deviceHooks.isSameDevice, return true);
|
|
|
|
|
return s_deviceHooks.isSameDevice(*this, other);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-13 11:11:29 +02:00
|
|
|
bool FilePath::isSameFile(const FilePath &other) const
|
|
|
|
|
{
|
|
|
|
|
if (*this == other)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if (!isSameDevice(other))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const QByteArray fileId = fileAccess()->fileId(*this);
|
|
|
|
|
const QByteArray otherFileId = fileAccess()->fileId(other);
|
|
|
|
|
if (fileId.isEmpty() || otherFileId.isEmpty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (fileId == otherFileId)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
/// \returns an empty FilePath if this is not a symbolic linl
|
|
|
|
|
FilePath FilePath::symLinkTarget() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->symLinkTarget(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-10-08 10:31:30 +02:00
|
|
|
QString FilePath::mapToDevicePath() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->mapToDevicePath(*this);
|
2021-10-08 10:31:30 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
FilePath FilePath::withExecutableSuffix() const
|
|
|
|
|
{
|
2022-08-04 15:00:24 +02:00
|
|
|
return withNewPath(OsSpecificAspects::withExecutableSuffix(osType(), path()));
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-19 16:00:28 +02:00
|
|
|
static bool startsWithWindowsDriveLetterAndSlash(QStringView path)
|
|
|
|
|
{
|
|
|
|
|
return path.size() > 2 && (path[1] == ':' && path[2] == '/' && isWindowsDriveLetter(path[0]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FilePath::rootLength(const QStringView path)
|
|
|
|
|
{
|
|
|
|
|
if (path.size() == 0)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
if (path.size() == 1)
|
|
|
|
|
return path[0] == '/' ? 1 : 0;
|
|
|
|
|
|
|
|
|
|
if (path[0] == '/' && path[1] == '/') { // UNC, FIXME: Incomplete
|
|
|
|
|
if (path.size() == 2)
|
|
|
|
|
return 2; // case deviceless UNC root - assuming there's such a thing.
|
|
|
|
|
const int pos = path.indexOf('/', 2);
|
|
|
|
|
if (pos == -1)
|
|
|
|
|
return path.size(); // case //localhost
|
|
|
|
|
return pos + 1; // case //localhost/*
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (startsWithWindowsDriveLetterAndSlash(path))
|
|
|
|
|
return 3; // FIXME-ish: same assumption as elsewhere: we assume "x:/" only ever appears as root
|
|
|
|
|
|
|
|
|
|
if (path[0] == '/')
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FilePath::schemeAndHostLength(const QStringView path)
|
|
|
|
|
{
|
|
|
|
|
static const QLatin1String colonSlashSlash("://");
|
|
|
|
|
|
|
|
|
|
const int sep = path.indexOf(colonSlashSlash);
|
|
|
|
|
if (sep == -1)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
const int pos = path.indexOf('/', sep + 3);
|
|
|
|
|
if (pos == -1) // Just scheme://host
|
|
|
|
|
return path.size();
|
|
|
|
|
|
|
|
|
|
return pos + 1; // scheme://host/ plus something
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
/// 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 QString path = basePath + QLatin1String("/..");
|
2021-11-23 10:09:54 +01:00
|
|
|
const QString parent = doCleanPath(path);
|
2022-09-23 10:28:59 +02:00
|
|
|
if (parent == path)
|
|
|
|
|
return FilePath();
|
2021-07-16 11:16:45 +02:00
|
|
|
|
2022-08-04 15:00:24 +02:00
|
|
|
return withNewPath(parent);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FilePath::absolutePath() const
|
|
|
|
|
{
|
2022-09-21 12:44:49 +02:00
|
|
|
if (!needsDevice() && isEmpty())
|
|
|
|
|
return *this;
|
|
|
|
|
const FilePath parentPath = isAbsolutePath()
|
|
|
|
|
? parentDir()
|
|
|
|
|
: FilePath::currentWorkingPath().resolvePath(*this).parentDir();
|
2022-07-28 15:59:52 +02:00
|
|
|
return parentPath.isEmpty() ? *this : parentPath;
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FilePath::absoluteFilePath() const
|
|
|
|
|
{
|
2021-09-09 06:45:37 +02:00
|
|
|
if (isAbsolutePath())
|
2022-10-11 15:50:42 +02:00
|
|
|
return cleanPath();
|
2022-09-21 12:44:49 +02:00
|
|
|
if (!needsDevice() && isEmpty())
|
2022-10-11 15:50:42 +02:00
|
|
|
return cleanPath();
|
2022-07-28 15:59:52 +02:00
|
|
|
|
|
|
|
|
return FilePath::currentWorkingPath().resolvePath(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 11:16:44 +02:00
|
|
|
QString FilePath::specialPath(SpecialPathComponent component)
|
|
|
|
|
{
|
|
|
|
|
switch (component) {
|
|
|
|
|
case SpecialPathComponent::RootName:
|
|
|
|
|
return QLatin1String("__qtc_devices__");
|
|
|
|
|
case SpecialPathComponent::RootPath:
|
|
|
|
|
return (QDir::rootPath() + "__qtc_devices__");
|
|
|
|
|
case SpecialPathComponent::DeviceRootName:
|
|
|
|
|
return QLatin1String("device");
|
|
|
|
|
case SpecialPathComponent::DeviceRootPath:
|
|
|
|
|
return QDir::rootPath() + "__qtc_devices__/device";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTC_ASSERT(false, return {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FilePath::specialFilePath(SpecialPathComponent component)
|
|
|
|
|
{
|
|
|
|
|
return FilePath::fromString(specialPath(component));
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-24 07:19:54 +02:00
|
|
|
FilePath FilePath::normalizedPathName() const
|
2021-08-20 13:04:02 +02:00
|
|
|
{
|
|
|
|
|
FilePath result = *this;
|
|
|
|
|
if (!needsDevice()) // FIXME: Assumes no remote Windows and Mac for now.
|
2022-09-21 12:59:02 +02:00
|
|
|
result.setParts(scheme(), host(), FileUtils::normalizedPathName(path()));
|
2021-08-20 13:04:02 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-02 17:05:46 +02:00
|
|
|
QString FilePath::displayName(const QString &args) const
|
|
|
|
|
{
|
|
|
|
|
QString deviceName;
|
|
|
|
|
if (needsDevice()) {
|
2022-09-23 07:32:02 +02:00
|
|
|
QTC_ASSERT(s_deviceHooks.deviceDisplayName, return nativePath());
|
2022-06-02 17:05:46 +02:00
|
|
|
deviceName = s_deviceHooks.deviceDisplayName(*this);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-23 07:32:02 +02:00
|
|
|
const QString fullPath = nativePath();
|
2022-07-27 11:38:01 +02:00
|
|
|
|
2022-06-02 17:05:46 +02:00
|
|
|
if (args.isEmpty()) {
|
|
|
|
|
if (deviceName.isEmpty())
|
2022-07-27 11:38:01 +02:00
|
|
|
return fullPath;
|
2022-06-02 17:05:46 +02:00
|
|
|
|
|
|
|
|
return QCoreApplication::translate("Utils::FileUtils", "%1 on %2", "File on device")
|
2022-07-27 11:38:01 +02:00
|
|
|
.arg(fullPath, deviceName);
|
2022-06-02 17:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (deviceName.isEmpty())
|
2022-07-27 11:38:01 +02:00
|
|
|
return fullPath + ' ' + args;
|
2022-06-02 17:05:46 +02:00
|
|
|
|
|
|
|
|
return QCoreApplication::translate("Utils::FileUtils", "%1 %2 on %3", "File and args on device")
|
2022-07-27 11:38:01 +02:00
|
|
|
.arg(fullPath, args, deviceName);
|
2022-06-02 17:05:46 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-21 15:50:11 +02:00
|
|
|
/*!
|
2022-07-27 11:38:01 +02:00
|
|
|
\fn FilePath FilePath::fromString(const QString &filepath)
|
|
|
|
|
|
2022-06-21 15:50:11 +02:00
|
|
|
Constructs a FilePath from \a filepath
|
|
|
|
|
|
|
|
|
|
\a filepath is not checked for validity. It can be given in the following forms:
|
|
|
|
|
|
|
|
|
|
\list
|
|
|
|
|
\li /some/absolute/local/path
|
|
|
|
|
\li some/relative/path
|
|
|
|
|
\li scheme://host/absolute/path
|
|
|
|
|
\li scheme://host/./relative/path \note the ./ is verbatim part of the path
|
|
|
|
|
\endlist
|
|
|
|
|
|
|
|
|
|
Some decoding happens when parsing the \a filepath
|
|
|
|
|
A sequence %25 present in the host part is replaced by % in the host name,
|
|
|
|
|
a sequence %2f present in the host part is replaced by / in the host name.
|
|
|
|
|
|
|
|
|
|
The path part might consist of several parts separated by /, independent
|
|
|
|
|
of the platform or file system.
|
|
|
|
|
|
|
|
|
|
To create FilePath objects from strings possibly containing backslashes as
|
|
|
|
|
path separator, use \c fromUserInput.
|
|
|
|
|
|
|
|
|
|
\sa toString, fromUserInput
|
2022-07-27 11:38:01 +02:00
|
|
|
*/
|
2022-08-04 16:35:31 +02:00
|
|
|
FilePath FilePath::fromString(const QString &filepath)
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
|
|
|
|
FilePath fn;
|
2022-08-04 16:35:31 +02:00
|
|
|
fn.setFromString(filepath);
|
2021-08-10 16:19:02 +02:00
|
|
|
return fn;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-04 16:35:31 +02:00
|
|
|
bool isWindowsDriveLetter(QChar ch)
|
|
|
|
|
{
|
2022-07-26 09:36:13 +02:00
|
|
|
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-13 08:58:31 +02:00
|
|
|
void FilePath::setPath(QStringView path)
|
2022-07-27 11:38:01 +02:00
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
setParts(scheme(), host(), path);
|
2022-08-04 16:35:31 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-13 11:52:44 +02:00
|
|
|
void FilePath::setFromString(const QString &unnormalizedFileName)
|
2021-08-10 16:19:02 +02:00
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
static const QStringView qtcDevSlash(u"__qtc_devices__/");
|
|
|
|
|
static const QStringView colonSlashSlash(u"://");
|
2022-05-31 11:16:44 +02:00
|
|
|
|
2022-09-13 11:52:44 +02:00
|
|
|
QString fileName = unnormalizedFileName;
|
|
|
|
|
if (fileName.contains('\\'))
|
|
|
|
|
fileName.replace('\\', '/');
|
|
|
|
|
|
2022-07-29 08:38:35 +02:00
|
|
|
const QChar slash('/');
|
2022-09-13 11:52:44 +02:00
|
|
|
const QStringView fileNameView(fileName);
|
2022-05-31 11:16:44 +02:00
|
|
|
|
2022-09-13 11:40:42 +02:00
|
|
|
bool startsWithQtcSlashDev = false;
|
|
|
|
|
QStringView withoutQtcDeviceRoot = fileNameView;
|
|
|
|
|
if (fileNameView.startsWith('/') && fileNameView.mid(1).startsWith(qtcDevSlash)) {
|
|
|
|
|
startsWithQtcSlashDev = true;
|
|
|
|
|
withoutQtcDeviceRoot = withoutQtcDeviceRoot.mid(1 + qtcDevSlash.size());
|
|
|
|
|
} else if (fileNameView.size() > 3 && isWindowsDriveLetter(fileNameView.at(0))
|
|
|
|
|
&& fileNameView.at(1) == ':' && fileNameView.mid(3).startsWith(qtcDevSlash)) {
|
|
|
|
|
startsWithQtcSlashDev = true;
|
|
|
|
|
withoutQtcDeviceRoot = withoutQtcDeviceRoot.mid(3 + qtcDevSlash.size());
|
2022-09-16 12:24:13 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-13 11:40:42 +02:00
|
|
|
if (startsWithQtcSlashDev) {
|
|
|
|
|
const int firstSlash = withoutQtcDeviceRoot.indexOf(slash);
|
|
|
|
|
|
|
|
|
|
if (firstSlash != -1) {
|
2022-09-21 12:59:02 +02:00
|
|
|
QString scheme = withoutQtcDeviceRoot.left(firstSlash).toString();
|
2022-09-13 11:40:42 +02:00
|
|
|
const int secondSlash = withoutQtcDeviceRoot.indexOf(slash, firstSlash + 1);
|
2022-09-21 12:59:02 +02:00
|
|
|
QString host = withoutQtcDeviceRoot.mid(firstSlash + 1, secondSlash - firstSlash - 1)
|
2022-09-13 11:40:42 +02:00
|
|
|
.toString();
|
|
|
|
|
if (secondSlash != -1) {
|
|
|
|
|
QStringView path = withoutQtcDeviceRoot.mid(secondSlash);
|
2022-09-21 12:59:02 +02:00
|
|
|
setParts(scheme, host, path);
|
2022-07-26 13:32:07 +02:00
|
|
|
return;
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
2022-07-27 11:38:01 +02:00
|
|
|
|
2022-09-21 12:59:02 +02:00
|
|
|
setParts(scheme, host, u"/");
|
2022-05-31 11:16:44 +02:00
|
|
|
return;
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
2022-09-13 11:40:42 +02:00
|
|
|
|
2022-09-21 12:59:02 +02:00
|
|
|
setParts({}, {}, fileName);
|
2022-09-13 11:40:42 +02:00
|
|
|
return;
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
2022-05-31 11:16:44 +02:00
|
|
|
|
2022-09-13 11:52:44 +02:00
|
|
|
const int firstSlash = fileName.indexOf(slash);
|
|
|
|
|
const int schemeEnd = fileName.indexOf(colonSlashSlash);
|
2022-05-31 11:16:44 +02:00
|
|
|
if (schemeEnd != -1 && schemeEnd < firstSlash) {
|
|
|
|
|
// This is a pseudo Url, we can't use QUrl here sadly.
|
2022-09-21 12:59:02 +02:00
|
|
|
QString scheme = fileName.left(schemeEnd);
|
2022-09-13 11:52:44 +02:00
|
|
|
const int hostEnd = fileName.indexOf(slash, schemeEnd + 3);
|
2022-09-21 12:59:02 +02:00
|
|
|
QString host = fileName.mid(schemeEnd + 3, hostEnd - schemeEnd - 3);
|
|
|
|
|
setParts(scheme, host, hostEnd != -1 ? QStringView(fileName).mid(hostEnd) : QStringView());
|
2022-05-31 11:16:44 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-21 12:59:02 +02:00
|
|
|
setParts({}, {}, fileName);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-10 17:32:56 +02:00
|
|
|
DeviceFileAccess *FilePath::fileAccess() const
|
|
|
|
|
{
|
|
|
|
|
if (!needsDevice())
|
|
|
|
|
return DesktopDeviceFileAccess::instance();
|
|
|
|
|
|
|
|
|
|
if (!s_deviceHooks.fileAccess) {
|
|
|
|
|
// Happens during startup and in tst_fsengine
|
|
|
|
|
QTC_CHECK(false);
|
|
|
|
|
return DesktopDeviceFileAccess::instance();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static DeviceFileAccess dummy;
|
|
|
|
|
DeviceFileAccess *access = s_deviceHooks.fileAccess(*this);
|
|
|
|
|
QTC_ASSERT(access, return &dummy);
|
|
|
|
|
return access;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
/// 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);
|
|
|
|
|
|
2021-09-08 12:25:15 +02:00
|
|
|
FilePath rc = FilePath::fromString(filepath);
|
2021-07-16 11:16:45 +02:00
|
|
|
// Add extension unless user specified something else
|
|
|
|
|
const QChar dot = QLatin1Char('.');
|
2021-09-08 12:25:15 +02:00
|
|
|
if (!rc.fileName().contains(dot)) {
|
2021-07-16 11:16:45 +02:00
|
|
|
if (!defaultExtension.startsWith(dot))
|
2021-09-13 11:50:31 +02:00
|
|
|
rc = rc.stringAppended(dot);
|
|
|
|
|
rc = rc.stringAppended(defaultExtension);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
2021-09-08 12:25:15 +02:00
|
|
|
return rc;
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Constructs a FilePath from \a filePath
|
|
|
|
|
/// \a filePath is only passed through QDir::fromNativeSeparators
|
|
|
|
|
FilePath FilePath::fromUserInput(const QString &filePath)
|
|
|
|
|
{
|
2021-11-23 10:09:54 +01:00
|
|
|
QString clean = doCleanPath(filePath);
|
2021-07-16 11:16:45 +02:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::operator==(const FilePath &other) const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
return QString::compare(path(), other.path(), caseSensitivity()) == 0
|
|
|
|
|
&& host() == other.host()
|
|
|
|
|
&& scheme() == other.scheme();
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::operator!=(const FilePath &other) const
|
|
|
|
|
{
|
|
|
|
|
return !(*this == other);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::operator<(const FilePath &other) const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
const int cmp = QString::compare(path(), other.path(), caseSensitivity());
|
2021-07-16 11:16:45 +02:00
|
|
|
if (cmp != 0)
|
|
|
|
|
return cmp < 0;
|
2022-09-21 12:59:02 +02:00
|
|
|
if (host() != other.host())
|
|
|
|
|
return host() < other.host();
|
|
|
|
|
return scheme() < other.scheme();
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
{
|
2022-08-16 09:47:23 +02:00
|
|
|
return stringAppended(s);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// \returns whether FilePath is a child of \a s
|
|
|
|
|
bool FilePath::isChildOf(const FilePath &s) const
|
|
|
|
|
{
|
2022-10-14 10:53:03 +02:00
|
|
|
if (!s.isSameDevice(*this))
|
|
|
|
|
return false;
|
2021-07-16 11:16:45 +02:00
|
|
|
if (s.isEmpty())
|
|
|
|
|
return false;
|
2022-09-21 12:59:02 +02:00
|
|
|
if (!path().startsWith(s.path(), caseSensitivity()))
|
2021-07-16 11:16:45 +02:00
|
|
|
return false;
|
2022-09-21 12:59:02 +02:00
|
|
|
if (path().size() <= s.path().size())
|
2021-07-16 11:16:45 +02:00
|
|
|
return false;
|
|
|
|
|
// s is root, '/' was already tested in startsWith
|
2022-09-21 12:59:02 +02:00
|
|
|
if (s.path().endsWith(QLatin1Char('/')))
|
2021-07-16 11:16:45 +02:00
|
|
|
return true;
|
|
|
|
|
// s is a directory, next character should be '/' (/tmpdir is NOT a child of /tmp)
|
2022-09-21 12:59:02 +02:00
|
|
|
return s.path().isEmpty() || path().at(s.path().size()) == QLatin1Char('/');
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
/// \returns whether path() startsWith \a s
|
2021-07-16 11:16:45 +02:00
|
|
|
bool FilePath::startsWith(const QString &s) const
|
|
|
|
|
{
|
2022-07-27 11:38:01 +02:00
|
|
|
return path().startsWith(s, caseSensitivity());
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
/*!
|
|
|
|
|
* \param s The string to check for at the end of the path.
|
|
|
|
|
* \returns whether FilePath endsWith \a s
|
|
|
|
|
*/
|
2021-07-16 11:16:45 +02:00
|
|
|
bool FilePath::endsWith(const QString &s) const
|
|
|
|
|
{
|
2022-07-27 11:38:01 +02:00
|
|
|
return path().endsWith(s, caseSensitivity());
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
/*!
|
|
|
|
|
* \brief Checks whether the FilePath starts with a drive letter.
|
|
|
|
|
* Defaults to \c false if it is a non-Windows host or represents a path on device
|
|
|
|
|
* \returns whether FilePath starts with a drive letter
|
|
|
|
|
*/
|
2021-08-05 09:01:11 +02:00
|
|
|
bool FilePath::startsWithDriveLetter() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
return !needsDevice() && path().size() >= 2 && isWindowsDriveLetter(path()[0]) && path().at(1) == ':';
|
2021-08-05 09:01:11 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
/*!
|
|
|
|
|
* \brief Relative path from \a parent to this.
|
|
|
|
|
* Returns a empty FilePath if this is not a child of \p parent.
|
|
|
|
|
* That is, this never returns a path starting with "../"
|
|
|
|
|
* \param parent The Parent to calculate the relative path to.
|
|
|
|
|
* \returns The relative path of this to \p parent if this is a child of \p parent.
|
|
|
|
|
*/
|
2021-07-16 11:16:45 +02:00
|
|
|
FilePath FilePath::relativeChildPath(const FilePath &parent) const
|
|
|
|
|
{
|
|
|
|
|
FilePath res;
|
2022-07-27 11:38:01 +02:00
|
|
|
if (isChildOf(parent)) {
|
2022-09-21 12:59:02 +02:00
|
|
|
QString p = path().mid(parent.path().size());
|
|
|
|
|
if (p.startsWith('/'))
|
|
|
|
|
p = p.mid(1);
|
|
|
|
|
res.setParts(scheme(), host(), p);
|
2022-07-27 11:38:01 +02:00
|
|
|
}
|
2021-07-16 11:16:45 +02:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-18 14:38:29 +02:00
|
|
|
/// \returns the relativePath of FilePath from a given \a anchor.
|
2021-07-16 11:16:45 +02:00
|
|
|
/// 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".
|
|
|
|
|
///
|
2022-10-18 14:38:29 +02:00
|
|
|
FilePath FilePath::relativePathFrom(const FilePath &anchor) const
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
2022-10-14 15:57:38 +02:00
|
|
|
QTC_ASSERT(isSameDevice(anchor), return *this);
|
2022-07-27 11:38:01 +02:00
|
|
|
|
2022-10-14 15:57:38 +02:00
|
|
|
FilePath absPath;
|
2021-07-16 11:16:45 +02:00
|
|
|
QString filename;
|
2022-10-14 15:57:38 +02:00
|
|
|
if (isFile()) {
|
|
|
|
|
absPath = absolutePath();
|
|
|
|
|
filename = fileName();
|
|
|
|
|
} else if (isDir()) {
|
|
|
|
|
absPath = absoluteFilePath();
|
2021-07-16 11:16:45 +02:00
|
|
|
} else {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
2022-10-14 15:57:38 +02:00
|
|
|
FilePath absoluteAnchorPath;
|
|
|
|
|
if (anchor.isFile())
|
|
|
|
|
absoluteAnchorPath = anchor.absolutePath();
|
|
|
|
|
else if (anchor.isDir())
|
|
|
|
|
absoluteAnchorPath = anchor.absoluteFilePath();
|
2021-07-16 11:16:45 +02:00
|
|
|
else
|
|
|
|
|
return {};
|
2022-10-14 15:57:38 +02:00
|
|
|
QString relativeFilePath = calcRelativePath(absPath.path(), absoluteAnchorPath.path());
|
2021-07-16 11:16:45 +02:00
|
|
|
if (!filename.isEmpty()) {
|
2021-08-17 08:49:57 +02:00
|
|
|
if (relativeFilePath == ".")
|
|
|
|
|
relativeFilePath.clear();
|
2021-07-16 11:16:45 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2021-08-17 08:49:57 +02:00
|
|
|
if (relativePath.isEmpty())
|
|
|
|
|
return QString(".");
|
2021-07-16 11:16:45 +02:00
|
|
|
return relativePath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
2022-07-27 11:38:01 +02:00
|
|
|
* \brief Returns a path corresponding to the current object on the
|
|
|
|
|
* same device as \a deviceTemplate. The FilePath needs to be local.
|
|
|
|
|
*
|
|
|
|
|
* Example usage:
|
|
|
|
|
* \code
|
|
|
|
|
* localDir = FilePath("/tmp/workingdir");
|
|
|
|
|
* executable = FilePath::fromUrl("docker://123/bin/ls")
|
|
|
|
|
* realDir = localDir.onDevice(executable)
|
|
|
|
|
* assert(realDir == FilePath::fromUrl("docker://123/tmp/workingdir"))
|
|
|
|
|
* \endcode
|
|
|
|
|
*
|
|
|
|
|
* \param deviceTemplate A path from which the host and scheme is taken.
|
|
|
|
|
*
|
|
|
|
|
* \returns A path on the same device as \a deviceTemplate.
|
2021-07-16 11:16:45 +02:00
|
|
|
*/
|
|
|
|
|
FilePath FilePath::onDevice(const FilePath &deviceTemplate) const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
const bool sameDevice = scheme() == deviceTemplate.scheme() && host() == deviceTemplate.host();
|
2021-10-28 07:47:48 +02:00
|
|
|
if (sameDevice)
|
|
|
|
|
return *this;
|
2021-10-25 18:16:27 +02:00
|
|
|
// TODO: converting paths between different non local devices is still unsupported
|
2021-10-28 07:47:48 +02:00
|
|
|
QTC_CHECK(!needsDevice());
|
2021-07-16 11:16:45 +02:00
|
|
|
FilePath res;
|
2022-09-21 12:59:02 +02:00
|
|
|
res.setParts(deviceTemplate.scheme(), deviceTemplate.host(), path());
|
2022-09-13 08:58:31 +02:00
|
|
|
res.setPath(res.mapToDevicePath());
|
2021-07-16 11:16:45 +02:00
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
Returns a FilePath with local path \a newPath on the same device
|
|
|
|
|
as the current object.
|
|
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
\code
|
2021-08-10 16:19:02 +02:00
|
|
|
devicePath = FilePath("docker://123/tmp");
|
2021-07-16 11:16:45 +02:00
|
|
|
newPath = devicePath.withNewPath("/bin/ls");
|
|
|
|
|
assert(realDir == FilePath::fromUrl("docker://123/bin/ls"))
|
|
|
|
|
\endcode
|
|
|
|
|
*/
|
|
|
|
|
FilePath FilePath::withNewPath(const QString &newPath) const
|
|
|
|
|
{
|
|
|
|
|
FilePath res;
|
2022-09-21 12:59:02 +02:00
|
|
|
res.setParts(scheme(), host(), newPath);
|
2021-07-16 11:16:45 +02:00
|
|
|
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);
|
2021-08-09 16:06:45 +02:00
|
|
|
fullPath = binary.searchInDirectories(binary.deviceEnvironment().path());
|
2021-07-16 11:16:45 +02:00
|
|
|
assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make"))
|
|
|
|
|
\endcode
|
|
|
|
|
*/
|
2021-08-09 16:06:45 +02:00
|
|
|
FilePath FilePath::searchInDirectories(const FilePaths &dirs) const
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
if (isAbsolutePath())
|
|
|
|
|
return *this;
|
|
|
|
|
// FIXME: Ramp down use.
|
|
|
|
|
QTC_ASSERT(!needsDevice(), return {});
|
2021-10-04 10:59:41 +02:00
|
|
|
return Environment::systemEnvironment().searchInDirectories(path(), dirs);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-30 09:34:05 +02:00
|
|
|
FilePath FilePath::searchInPath(const FilePaths &additionalDirs, PathAmending amending) const
|
|
|
|
|
{
|
2022-09-29 09:42:49 +02:00
|
|
|
if (isAbsolutePath())
|
|
|
|
|
return *this;
|
2022-10-10 17:32:56 +02:00
|
|
|
// FIXME: Ramp down use.
|
2021-09-30 09:34:05 +02:00
|
|
|
FilePaths directories = deviceEnvironment().path();
|
|
|
|
|
if (!additionalDirs.isEmpty()) {
|
|
|
|
|
if (amending == AppendToPath)
|
|
|
|
|
directories.append(additionalDirs);
|
|
|
|
|
else
|
|
|
|
|
directories = additionalDirs + directories;
|
|
|
|
|
}
|
2022-10-10 17:32:56 +02:00
|
|
|
QTC_ASSERT(!needsDevice(), return {});
|
|
|
|
|
return Environment::systemEnvironment().searchInDirectories(path(), directories);
|
2021-08-17 14:49:19 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
Environment FilePath::deviceEnvironment() const
|
|
|
|
|
{
|
|
|
|
|
if (needsDevice()) {
|
|
|
|
|
QTC_ASSERT(s_deviceHooks.environment, return {});
|
|
|
|
|
return s_deviceHooks.environment(*this);
|
|
|
|
|
}
|
|
|
|
|
return Environment::systemEnvironment();
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-21 15:58:09 +02:00
|
|
|
QString FilePath::formatFilePaths(const FilePaths &files, const QString &separator)
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
2022-05-24 00:40:44 +02:00
|
|
|
const QStringList nativeFiles = transform(files, &FilePath::toUserOutput);
|
2021-07-16 11:16:45 +02:00
|
|
|
return nativeFiles.join(separator);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-21 15:58:09 +02:00
|
|
|
void FilePath::removeDuplicates(FilePaths &files)
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
|
|
|
|
// FIXME: Improve.
|
2022-08-02 09:27:21 +02:00
|
|
|
// FIXME: This drops the osType information, which is not correct.
|
2022-05-24 00:40:44 +02:00
|
|
|
QStringList list = transform<QStringList>(files, &FilePath::toString);
|
2021-07-16 11:16:45 +02:00
|
|
|
list.removeDuplicates();
|
2022-08-02 09:27:21 +02:00
|
|
|
files = FileUtils::toFilePathList(list);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-21 15:58:09 +02:00
|
|
|
void FilePath::sort(FilePaths &files)
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
|
|
|
|
// FIXME: Improve.
|
2022-08-02 09:27:21 +02:00
|
|
|
// FIXME: This drops the osType information, which is not correct.
|
2022-05-24 00:40:44 +02:00
|
|
|
QStringList list = transform<QStringList>(files, &FilePath::toString);
|
2021-07-16 11:16:45 +02:00
|
|
|
list.sort();
|
2022-08-02 09:27:21 +02:00
|
|
|
files = FileUtils::toFilePathList(list);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
void join(QString &left, const QString &right)
|
|
|
|
|
{
|
|
|
|
|
QStringView r(right);
|
|
|
|
|
if (r.startsWith('/'))
|
|
|
|
|
r = r.mid(1);
|
|
|
|
|
|
|
|
|
|
if (left.isEmpty() || left.endsWith('/'))
|
|
|
|
|
left += r;
|
|
|
|
|
else
|
|
|
|
|
left += '/' + r;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
FilePath FilePath::pathAppended(const QString &path) const
|
|
|
|
|
{
|
|
|
|
|
if (path.isEmpty())
|
2022-07-27 11:38:01 +02:00
|
|
|
return *this;
|
2021-08-13 11:39:54 +02:00
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
FilePath other = FilePath::fromString(path);
|
2021-08-13 11:39:54 +02:00
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
if (isEmpty()) {
|
|
|
|
|
return other;
|
2021-08-13 11:39:54 +02:00
|
|
|
}
|
2022-09-21 12:59:02 +02:00
|
|
|
|
|
|
|
|
QString p = this->path();
|
|
|
|
|
join(p, other.path());
|
|
|
|
|
|
|
|
|
|
return withNewPath(p);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FilePath::stringAppended(const QString &str) const
|
|
|
|
|
{
|
2022-08-16 09:47:23 +02:00
|
|
|
return FilePath::fromString(toString() + str);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
size_t FilePath::hash(uint seed) const
|
2021-07-16 11:16:45 +02:00
|
|
|
{
|
2022-05-24 00:40:44 +02:00
|
|
|
if (HostOsInfo::fileNameCaseSensitivity() == Qt::CaseInsensitive)
|
2022-07-27 11:38:01 +02:00
|
|
|
return qHash(path().toCaseFolded(), seed);
|
|
|
|
|
return qHash(path(), seed);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QDateTime FilePath::lastModified() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->lastModified(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFile::Permissions FilePath::permissions() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->permissions(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-12 10:29:18 +02:00
|
|
|
bool FilePath::setPermissions(QFile::Permissions permissions) const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->setPermissions(*this, permissions);
|
2021-08-12 10:29:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
OsType FilePath::osType() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->osType(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::removeFile() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->removeFile(*this);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
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
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->removeRecursively(*this, error);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FilePath::copyFile(const FilePath &target) const
|
|
|
|
|
{
|
2021-08-30 11:37:58 +02:00
|
|
|
if (host() != target.host()) {
|
|
|
|
|
// FIXME: This does not scale.
|
2022-09-09 13:48:08 +02:00
|
|
|
const std::optional<QByteArray> ba = fileContents();
|
|
|
|
|
if (!ba)
|
|
|
|
|
return false;
|
|
|
|
|
return target.writeFileContents(*ba);
|
2021-08-30 11:37:58 +02:00
|
|
|
}
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->copyFile(*this, target);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-30 15:42:21 +02:00
|
|
|
void FilePath::asyncCopyFile(const std::function<void(bool)> &cont, const FilePath &target) const
|
|
|
|
|
{
|
|
|
|
|
if (host() != target.host()) {
|
2022-09-09 13:48:08 +02:00
|
|
|
asyncFileContents([cont, target](const std::optional<QByteArray> &ba) {
|
|
|
|
|
if (ba)
|
|
|
|
|
target.asyncWriteFileContents(cont, *ba);
|
2021-08-30 15:42:21 +02:00
|
|
|
});
|
2022-10-10 17:32:56 +02:00
|
|
|
return;
|
2021-08-30 15:42:21 +02:00
|
|
|
}
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->asyncCopyFile(*this, cont, target);
|
2021-08-30 15:42:21 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
bool FilePath::renameFile(const FilePath &target) const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->renameFile(*this, target);
|
2021-07-16 11:16:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-23 12:06:38 +02:00
|
|
|
qint64 FilePath::fileSize() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->fileSize(*this);
|
2021-07-23 12:06:38 +02:00
|
|
|
}
|
|
|
|
|
|
2022-01-04 15:46:55 +01:00
|
|
|
qint64 FilePath::bytesAvailable() const
|
|
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
return fileAccess()->bytesAvailable(*this);
|
2022-07-27 11:38:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Checks if this is newer than \p timeStamp
|
|
|
|
|
* \param timeStamp The time stamp to compare with
|
|
|
|
|
* \returns true if this is newer than \p timeStamp.
|
|
|
|
|
* 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
|
|
|
|
|
* \p 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Returns the caseSensitivity of the path.
|
|
|
|
|
* \returns The caseSensitivity of the path.
|
|
|
|
|
* This is currently only based on the Host OS.
|
|
|
|
|
* For device paths, \c Qt::CaseSensitive is always returned.
|
|
|
|
|
*/
|
|
|
|
|
Qt::CaseSensitivity FilePath::caseSensitivity() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
if (scheme().isEmpty())
|
2022-07-27 11:38:01 +02:00
|
|
|
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 "most things are case-sensitive".
|
|
|
|
|
return Qt::CaseSensitive;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Recursively resolves possibly present symlinks in this file name.
|
2022-09-02 15:25:41 +02:00
|
|
|
* On Windows, also resolves SUBST and re-mounted NTFS drives.
|
2022-07-27 11:38:01 +02:00
|
|
|
* 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;
|
|
|
|
|
}
|
2022-09-02 15:25:41 +02:00
|
|
|
|
|
|
|
|
#ifdef Q_OS_WINDOWS
|
2022-09-06 11:21:01 +02:00
|
|
|
DWORD flagsAndAttrs = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
|
if (isDir())
|
|
|
|
|
flagsAndAttrs |= FILE_FLAG_BACKUP_SEMANTICS;
|
|
|
|
|
const HANDLE fileHandle = CreateFile(
|
|
|
|
|
toUserOutput().toStdWString().c_str(),
|
|
|
|
|
GENERIC_READ,
|
|
|
|
|
FILE_SHARE_READ,
|
|
|
|
|
nullptr,
|
|
|
|
|
OPEN_EXISTING,
|
|
|
|
|
flagsAndAttrs,
|
|
|
|
|
nullptr);
|
|
|
|
|
if (fileHandle != INVALID_HANDLE_VALUE) {
|
2022-09-02 15:25:41 +02:00
|
|
|
TCHAR normalizedPath[MAX_PATH];
|
|
|
|
|
const auto length = GetFinalPathNameByHandleW(
|
2022-09-06 11:21:01 +02:00
|
|
|
fileHandle,
|
2022-09-02 15:25:41 +02:00
|
|
|
normalizedPath,
|
|
|
|
|
MAX_PATH,
|
|
|
|
|
FILE_NAME_NORMALIZED);
|
2022-09-06 11:21:01 +02:00
|
|
|
CloseHandle(fileHandle);
|
2022-09-02 15:25:41 +02:00
|
|
|
if (length > 0)
|
|
|
|
|
return fromUserInput(QString::fromStdWString(std::wstring(normalizedPath, length)));
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
const QString result = toFileInfo().canonicalFilePath();
|
2022-09-02 15:25:41 +02:00
|
|
|
if (!result.isEmpty())
|
|
|
|
|
return fromString(result);
|
|
|
|
|
|
|
|
|
|
return *this;
|
2022-07-27 11:38:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FilePath FilePath::operator/(const QString &str) const
|
|
|
|
|
{
|
|
|
|
|
return pathAppended(str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Clears all parts of the FilePath.
|
|
|
|
|
*/
|
|
|
|
|
void FilePath::clear()
|
|
|
|
|
{
|
2022-08-11 16:32:19 +02:00
|
|
|
*this = {};
|
2022-07-27 11:38:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Checks if the path() is empty.
|
|
|
|
|
* \returns true if the path() is empty.
|
|
|
|
|
* The Host and Scheme of the part are ignored.
|
|
|
|
|
*/
|
|
|
|
|
bool FilePath::isEmpty() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
return m_pathLen == 0;
|
2022-07-27 11:38:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Converts the path to a possibly shortened path with native separators.
|
|
|
|
|
* 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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Checks whether the path is relative
|
|
|
|
|
* \returns true if the path is relative.
|
|
|
|
|
*/
|
|
|
|
|
bool FilePath::isRelativePath() const
|
|
|
|
|
{
|
2022-09-21 12:59:02 +02:00
|
|
|
if (path().startsWith('/'))
|
2022-09-13 08:58:31 +02:00
|
|
|
return false;
|
2022-09-21 12:59:02 +02:00
|
|
|
if (path().size() > 1 && isWindowsDriveLetter(path()[0]) && path().at(1) == ':')
|
2022-09-13 08:58:31 +02:00
|
|
|
return false;
|
2022-10-07 15:52:41 +02:00
|
|
|
if (path().startsWith(":/")) // QRC
|
|
|
|
|
return false;
|
2022-09-13 08:58:31 +02:00
|
|
|
return true;
|
2022-07-27 11:38:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Appends the tail to this, if the tail is a relative path.
|
|
|
|
|
* \param tail The tail to append.
|
|
|
|
|
* \returns Returns tail if tail is absolute, otherwise this + tail.
|
|
|
|
|
*/
|
|
|
|
|
FilePath FilePath::resolvePath(const FilePath &tail) const
|
|
|
|
|
{
|
2022-08-11 16:32:19 +02:00
|
|
|
if (tail.isRelativePath())
|
2022-09-13 08:58:31 +02:00
|
|
|
return pathAppended(tail.path());
|
2022-08-11 16:32:19 +02:00
|
|
|
return tail;
|
2022-07-27 11:38:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Appends the tail to this, if the tail is a relative path.
|
|
|
|
|
* \param tail The tail to append.
|
|
|
|
|
* \returns Returns tail if tail is absolute, otherwise this + tail.
|
|
|
|
|
*/
|
|
|
|
|
FilePath FilePath::resolvePath(const QString &tail) const
|
|
|
|
|
{
|
|
|
|
|
FilePath tailPath = FilePath::fromString(doCleanPath(tail));
|
|
|
|
|
return resolvePath(tailPath);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 15:04:44 +02:00
|
|
|
// Cleans path part 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".
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
FilePath FilePath::cleanPath() const
|
|
|
|
|
{
|
2022-08-04 15:00:24 +02:00
|
|
|
return withNewPath(doCleanPath(path()));
|
2022-07-27 11:38:01 +02:00
|
|
|
}
|
2022-01-04 15:46:55 +01:00
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
QTextStream &operator<<(QTextStream &s, const FilePath &fn)
|
|
|
|
|
{
|
|
|
|
|
return s << fn.toString();
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 15:04:44 +02:00
|
|
|
static QString normalizePathSegmentHelper(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
const int len = name.length();
|
|
|
|
|
|
|
|
|
|
if (len == 0)
|
|
|
|
|
return name;
|
|
|
|
|
|
|
|
|
|
int i = len - 1;
|
|
|
|
|
QVarLengthArray<char16_t> outVector(len);
|
|
|
|
|
int used = len;
|
|
|
|
|
char16_t *out = outVector.data();
|
|
|
|
|
const ushort *p = reinterpret_cast<const ushort *>(name.data());
|
|
|
|
|
const ushort *prefix = p;
|
|
|
|
|
int up = 0;
|
|
|
|
|
|
|
|
|
|
const int prefixLength = name.at(0) == u'/' ? 1 : 0;
|
|
|
|
|
|
|
|
|
|
p += prefixLength;
|
|
|
|
|
i -= prefixLength;
|
|
|
|
|
|
|
|
|
|
// replicate trailing slash (i > 0 checks for emptiness of input string p)
|
|
|
|
|
// except for remote paths because there can be /../ or /./ ending
|
|
|
|
|
if (i > 0 && p[i] == '/') {
|
|
|
|
|
out[--used] = '/';
|
|
|
|
|
--i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (i >= 0) {
|
|
|
|
|
if (p[i] == '/') {
|
|
|
|
|
--i;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// remove current directory
|
|
|
|
|
if (p[i] == '.' && (i == 0 || p[i-1] == '/')) {
|
|
|
|
|
--i;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// detect up dir
|
|
|
|
|
if (i >= 1 && p[i] == '.' && p[i-1] == '.' && (i < 2 || p[i - 2] == '/')) {
|
|
|
|
|
++up;
|
|
|
|
|
i -= i >= 2 ? 3 : 2;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// prepend a slash before copying when not empty
|
|
|
|
|
if (!up && used != len && out[used] != '/')
|
|
|
|
|
out[--used] = '/';
|
|
|
|
|
|
|
|
|
|
// skip or copy
|
|
|
|
|
while (i >= 0) {
|
|
|
|
|
if (p[i] == '/') {
|
|
|
|
|
--i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// actual copy
|
|
|
|
|
if (!up)
|
|
|
|
|
out[--used] = p[i];
|
|
|
|
|
--i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// decrement up after copying/skipping
|
|
|
|
|
if (up)
|
|
|
|
|
--up;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Indicate failure when ".." are left over for an absolute path.
|
|
|
|
|
// if (ok)
|
|
|
|
|
// *ok = prefixLength == 0 || up == 0;
|
|
|
|
|
|
|
|
|
|
// add remaining '..'
|
|
|
|
|
while (up) {
|
|
|
|
|
if (used != len && out[used] != '/') // is not empty and there isn't already a '/'
|
|
|
|
|
out[--used] = '/';
|
|
|
|
|
out[--used] = '.';
|
|
|
|
|
out[--used] = '.';
|
|
|
|
|
--up;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isEmpty = used == len;
|
|
|
|
|
|
|
|
|
|
if (prefixLength) {
|
|
|
|
|
if (!isEmpty && out[used] == '/') {
|
|
|
|
|
// Even though there is a prefix the out string is a slash. This happens, if the input
|
|
|
|
|
// string only consists of a prefix followed by one or more slashes. Just skip the slash.
|
|
|
|
|
++used;
|
|
|
|
|
}
|
|
|
|
|
for (int i = prefixLength - 1; i >= 0; --i)
|
|
|
|
|
out[--used] = prefix[i];
|
|
|
|
|
} else {
|
|
|
|
|
if (isEmpty) {
|
|
|
|
|
// After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return
|
|
|
|
|
// a dot in that case.
|
|
|
|
|
out[--used] = '.';
|
|
|
|
|
} else if (out[used] == '/') {
|
|
|
|
|
// After parsing the input string, out only contains a slash. That happens whenever all
|
|
|
|
|
// parts are resolved and there is a trailing slash ("./" or "foo/../" for example).
|
|
|
|
|
// Prepend a dot to have the correct return value.
|
|
|
|
|
out[--used] = '.';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If path was not modified return the original value
|
|
|
|
|
if (used == 0)
|
|
|
|
|
return name;
|
|
|
|
|
return QString::fromUtf16(out + used, len - used);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString doCleanPath(const QString &input_)
|
|
|
|
|
{
|
|
|
|
|
QString input = input_;
|
|
|
|
|
if (input.contains('\\'))
|
|
|
|
|
input.replace('\\', '/');
|
|
|
|
|
|
|
|
|
|
if (input.startsWith("//?/")) {
|
|
|
|
|
input = input.mid(4);
|
|
|
|
|
if (input.startsWith("UNC/"))
|
|
|
|
|
input = '/' + input.mid(3); // trick it into reporting two slashs at start
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int prefixLen = 0;
|
|
|
|
|
const int shLen = FilePath::schemeAndHostLength(input);
|
|
|
|
|
if (shLen > 0) {
|
|
|
|
|
prefixLen = shLen + FilePath::rootLength(input.mid(shLen));
|
|
|
|
|
} else {
|
|
|
|
|
prefixLen = FilePath::rootLength(input);
|
|
|
|
|
if (prefixLen > 0 && input.at(prefixLen - 1) == '/')
|
|
|
|
|
--prefixLen;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString path = normalizePathSegmentHelper(input.mid(prefixLen));
|
|
|
|
|
|
|
|
|
|
// Strip away last slash except for root directories
|
|
|
|
|
if (path.size() > 1 && path.endsWith(u'/'))
|
|
|
|
|
path.chop(1);
|
|
|
|
|
|
|
|
|
|
return input.left(prefixLen) + path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// FileFilter
|
|
|
|
|
|
2022-01-21 12:22:54 +01:00
|
|
|
FileFilter::FileFilter(const QStringList &nameFilters,
|
2022-07-14 15:04:44 +02:00
|
|
|
const QDir::Filters fileFilters,
|
|
|
|
|
const QDirIterator::IteratorFlags flags)
|
2022-01-21 12:22:54 +01:00
|
|
|
: nameFilters(nameFilters),
|
|
|
|
|
fileFilters(fileFilters),
|
|
|
|
|
iteratorFlags(flags)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 15:42:14 +02:00
|
|
|
QStringList FileFilter::asFindArguments(const QString &path) const
|
2022-10-05 17:11:09 +02:00
|
|
|
{
|
|
|
|
|
QStringList arguments;
|
|
|
|
|
|
|
|
|
|
const QDir::Filters filters = fileFilters;
|
2022-10-05 15:42:14 +02:00
|
|
|
|
|
|
|
|
if (iteratorFlags.testFlag(QDirIterator::FollowSymlinks))
|
|
|
|
|
arguments << "-L";
|
2022-10-05 17:11:09 +02:00
|
|
|
else
|
2022-10-05 15:42:14 +02:00
|
|
|
arguments << "-H";
|
2022-10-05 17:11:09 +02:00
|
|
|
|
2022-10-05 15:42:14 +02:00
|
|
|
arguments << path;
|
2022-10-05 17:11:09 +02:00
|
|
|
|
|
|
|
|
if (!iteratorFlags.testFlag(QDirIterator::Subdirectories))
|
|
|
|
|
arguments.append({"-maxdepth", "1"});
|
|
|
|
|
|
|
|
|
|
QStringList filterOptions;
|
|
|
|
|
|
|
|
|
|
if (!(filters & QDir::Hidden))
|
|
|
|
|
filterOptions << "!" << "-name" << ".*";
|
|
|
|
|
|
2022-10-05 15:42:14 +02:00
|
|
|
QStringList typesToList;
|
|
|
|
|
|
2022-10-05 17:11:09 +02:00
|
|
|
QStringList filterFilesAndDirs;
|
2022-10-05 15:42:14 +02:00
|
|
|
if (filters.testFlag(QDir::Dirs))
|
2022-10-05 17:11:09 +02:00
|
|
|
filterFilesAndDirs << "-type" << "d";
|
2022-10-05 15:42:14 +02:00
|
|
|
if (filters.testFlag(QDir::Files)) {
|
2022-10-05 17:11:09 +02:00
|
|
|
if (!filterFilesAndDirs.isEmpty())
|
|
|
|
|
filterFilesAndDirs << "-o";
|
|
|
|
|
filterFilesAndDirs << "-type" << "f";
|
|
|
|
|
}
|
2022-10-05 15:42:14 +02:00
|
|
|
|
|
|
|
|
if (!filters.testFlag(QDir::NoSymLinks)) {
|
|
|
|
|
if (!filterFilesAndDirs.isEmpty())
|
|
|
|
|
filterFilesAndDirs << "-o";
|
|
|
|
|
filterFilesAndDirs << "-type" << "l";
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 17:11:09 +02:00
|
|
|
if (!filterFilesAndDirs.isEmpty())
|
|
|
|
|
filterOptions << "(" << filterFilesAndDirs << ")";
|
|
|
|
|
|
|
|
|
|
QStringList accessOptions;
|
|
|
|
|
if (filters & QDir::Readable)
|
|
|
|
|
accessOptions << "-readable";
|
|
|
|
|
if (filters & QDir::Writable) {
|
|
|
|
|
if (!accessOptions.isEmpty())
|
|
|
|
|
accessOptions << "-o";
|
|
|
|
|
accessOptions << "-writable";
|
|
|
|
|
}
|
|
|
|
|
if (filters & QDir::Executable) {
|
|
|
|
|
if (!accessOptions.isEmpty())
|
|
|
|
|
accessOptions << "-o";
|
|
|
|
|
accessOptions << "-executable";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!accessOptions.isEmpty())
|
|
|
|
|
filterOptions << "(" << accessOptions << ")";
|
|
|
|
|
|
|
|
|
|
QTC_CHECK(filters ^ QDir::AllDirs);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::Drives);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::NoDot);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::NoDotDot);
|
|
|
|
|
QTC_CHECK(filters ^ QDir::System);
|
|
|
|
|
|
|
|
|
|
const QString nameOption = (filters & QDir::CaseSensitive) ? QString{"-name"}
|
|
|
|
|
: QString{"-iname"};
|
|
|
|
|
if (!nameFilters.isEmpty()) {
|
2022-10-13 08:55:50 +02:00
|
|
|
bool isFirst = true;
|
|
|
|
|
filterOptions << "(";
|
2022-10-05 17:11:09 +02:00
|
|
|
for (const QString ¤t : nameFilters) {
|
2022-10-13 08:55:50 +02:00
|
|
|
if (!isFirst)
|
2022-10-05 17:11:09 +02:00
|
|
|
filterOptions << "-o";
|
|
|
|
|
filterOptions << nameOption << current;
|
2022-10-13 08:55:50 +02:00
|
|
|
isFirst = false;
|
2022-10-05 17:11:09 +02:00
|
|
|
}
|
2022-10-13 08:55:50 +02:00
|
|
|
filterOptions << ")";
|
2022-10-05 17:11:09 +02:00
|
|
|
}
|
|
|
|
|
arguments << filterOptions;
|
|
|
|
|
return arguments;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:38:01 +02:00
|
|
|
DeviceFileHooks &DeviceFileHooks::instance()
|
|
|
|
|
{
|
|
|
|
|
return s_deviceHooks;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-16 11:16:45 +02:00
|
|
|
} // 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)
|
2022-06-10 11:57:29 +02:00
|
|
|
return hash<string>()(fn.toString().toCaseFolded().toStdString());
|
2021-07-16 11:16:45 +02:00
|
|
|
return hash<string>()(fn.toString().toStdString());
|
|
|
|
|
}
|
2022-07-27 11:38:01 +02:00
|
|
|
|
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
QDebug operator<<(QDebug dbg, const Utils::FilePath &c)
|
|
|
|
|
{
|
|
|
|
|
return dbg << c.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QT_END_NAMESPACE
|