mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-22 14:23:35 -07:00
Support UNC path capacity on windows
This commit is contained in:
parent
82c36aea89
commit
1cfeecd4aa
4 changed files with 117 additions and 13 deletions
|
@ -54,6 +54,7 @@ namespace
|
|||
{
|
||||
QString cleanPath(const QString &path)
|
||||
{
|
||||
|
||||
const bool hasSeparator = std::any_of(path.cbegin(), path.cend(), [](const QChar c)
|
||||
{
|
||||
return (c == u'/') || (c == u'\\');
|
||||
|
@ -74,8 +75,18 @@ namespace
|
|||
static_assert(Stringable<Path>);
|
||||
|
||||
Path::Path(const QString &pathStr)
|
||||
: m_pathStr {cleanPath(pathStr)}
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
if (validateUNCPath(pathStr))
|
||||
{
|
||||
auto [rootPath, filename] = splitUNCPath(pathStr);
|
||||
isUNCPath = true;
|
||||
rootStr = rootPath;
|
||||
m_pathStr = cleanPath(filename).replace(u"/"_s, u"\\"_s);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
m_pathStr = cleanPath(pathStr);
|
||||
}
|
||||
|
||||
Path::Path(const std::string &pathStr)
|
||||
|
@ -85,13 +96,14 @@ Path::Path(const std::string &pathStr)
|
|||
|
||||
bool Path::isValid() const
|
||||
{
|
||||
// does not support UNC path
|
||||
|
||||
if (isEmpty())
|
||||
return false;
|
||||
|
||||
// https://stackoverflow.com/a/31976060
|
||||
#if defined(Q_OS_WIN)
|
||||
if (isUNCPath)
|
||||
return true;
|
||||
|
||||
QStringView view = m_pathStr;
|
||||
if (hasDriveLetter(view))
|
||||
view = view.mid(3);
|
||||
|
@ -109,7 +121,7 @@ bool Path::isValid() const
|
|||
|
||||
bool Path::isEmpty() const
|
||||
{
|
||||
return m_pathStr.isEmpty();
|
||||
return isUNCPath ? false : m_pathStr.isEmpty();
|
||||
}
|
||||
|
||||
bool Path::isAbsolute() const
|
||||
|
@ -128,6 +140,36 @@ bool Path::isRelative() const
|
|||
return QDir::isRelativePath(m_pathStr);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
bool Path::validateUNCPath() const
|
||||
{
|
||||
return validateUNCPath(data());
|
||||
}
|
||||
|
||||
bool Path::validateUNCPath(const QString &pathStr) const
|
||||
{
|
||||
const QRegularExpression forbidden {u"[\\0-\\37:?\"*<>|:/]"_s}; // no drive letter allowed C:/
|
||||
const QRegularExpression pattern{uR"(^\\(\\[^\\]+){2,}[\\]*$)"_s}; // need raw \\ to match back slash
|
||||
return pattern.match(pathStr).hasMatch() && !pathStr.contains(forbidden);
|
||||
}
|
||||
|
||||
QPair<QString, QString> Path::splitUNCPath(const QString &pathStr) const
|
||||
{
|
||||
// only call this only if validateUNCPath() returns true
|
||||
int slashCount = 0;
|
||||
int index = 0;
|
||||
for (index = 0; index < pathStr.size(); ++index)
|
||||
{
|
||||
if (pathStr[index] == u'\\')
|
||||
{
|
||||
if (++slashCount == 4)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {pathStr.left(index), pathStr.right(pathStr.size() - index)};
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Path::exists() const
|
||||
{
|
||||
return !isEmpty() && QFileInfo::exists(m_pathStr);
|
||||
|
@ -135,8 +177,10 @@ bool Path::exists() const
|
|||
|
||||
Path Path::rootItem() const
|
||||
{
|
||||
// does not support UNC path
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (isUNCPath)
|
||||
return createUnchecked(rootStr, true);
|
||||
#endif
|
||||
const int slashIndex = m_pathStr.indexOf(u'/');
|
||||
if (slashIndex < 0)
|
||||
return *this;
|
||||
|
@ -154,8 +198,14 @@ Path Path::rootItem() const
|
|||
|
||||
Path Path::parentPath() const
|
||||
{
|
||||
// does not support UNC path
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (isUNCPath)
|
||||
{
|
||||
const int backSlashIndex = m_pathStr.lastIndexOf(u'\\');
|
||||
const QString parent = rootStr + m_pathStr.left(backSlashIndex);
|
||||
return createUnchecked(parent, true);
|
||||
}
|
||||
#endif
|
||||
const int slashIndex = m_pathStr.lastIndexOf(u'/');
|
||||
if (slashIndex == -1)
|
||||
return {};
|
||||
|
@ -241,12 +291,12 @@ Path Path::removedExtension(const QStringView ext) const
|
|||
|
||||
QString Path::data() const
|
||||
{
|
||||
return m_pathStr;
|
||||
return (isUNCPath ? (rootStr + m_pathStr) : m_pathStr);
|
||||
}
|
||||
|
||||
QString Path::toString() const
|
||||
{
|
||||
return QDir::toNativeSeparators(m_pathStr);
|
||||
return QDir::toNativeSeparators(data());
|
||||
}
|
||||
|
||||
std::filesystem::path Path::toStdFsPath() const
|
||||
|
@ -333,11 +383,11 @@ void Path::addRootFolder(PathList &filePaths, const Path &rootFolder)
|
|||
filePath = rootFolder / filePath;
|
||||
}
|
||||
|
||||
Path Path::createUnchecked(const QString &pathStr)
|
||||
Path Path::createUnchecked(const QString &pathStr, const bool isUNC)
|
||||
{
|
||||
Path path;
|
||||
path.m_pathStr = pathStr;
|
||||
|
||||
path.isUNCPath = isUNC;
|
||||
return path;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,12 @@ public:
|
|||
bool isEmpty() const;
|
||||
bool isAbsolute() const;
|
||||
bool isRelative() const;
|
||||
bool isUNCPath = false;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
bool validateUNCPath() const;
|
||||
bool validateUNCPath(const QString &pathStr) const;
|
||||
#endif
|
||||
|
||||
bool exists() const;
|
||||
|
||||
|
@ -86,9 +92,14 @@ public:
|
|||
private:
|
||||
// this constructor doesn't perform any checks
|
||||
// so it's intended for internal use only
|
||||
static Path createUnchecked(const QString &pathStr);
|
||||
static Path createUnchecked(const QString &pathStr, const bool isUNCPath = false);
|
||||
|
||||
QString m_pathStr;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
QString rootStr;
|
||||
QPair<QString, QString> splitUNCPath(const QString &pathStr) const;
|
||||
#endif
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Path)
|
||||
|
|
|
@ -209,6 +209,17 @@ Path Utils::Fs::toValidPath(const QString &name, const QString &pad)
|
|||
|
||||
qint64 Utils::Fs::freeDiskSpaceOnPath(const Path &path)
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
const auto wStrPath = path.data().toStdWString();
|
||||
if (path.isUNCPath)
|
||||
{
|
||||
ULARGE_INTEGER FreeBytesAvailable = {0};
|
||||
const BOOL ok = GetDiskFreeSpaceEx(wStrPath.c_str(), &FreeBytesAvailable, nullptr, nullptr);
|
||||
if (ok)
|
||||
return FreeBytesAvailable.QuadPart;
|
||||
}
|
||||
#endif
|
||||
|
||||
return QStorageInfo(path.data()).bytesAvailable();
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,10 @@ private slots:
|
|||
QVERIFY(Path(uR"(\\?\C:\)"_s) == Path(std::string(R"(\\?\C:\)")));
|
||||
|
||||
QVERIFY(Path(uR"(\\?\C:\abc)"_s) == Path(std::string(R"(\\?\C:\abc)")));
|
||||
|
||||
QVERIFY(Path(uR"(\\nas01\drive)"_s) == Path(std::string(R"(\\nas01\drive)")));
|
||||
QVERIFY(Path(uR"(\\nas01\drive\xxx)"_s) == Path(std::string(R"(\\nas01\drive\xxx)")));
|
||||
QVERIFY(Path(uR"(\\nas01\drive\xxx\\)"_s) == Path(std::string(R"(\\nas01\drive\xxx)")));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -109,11 +113,31 @@ private slots:
|
|||
QCOMPARE(Path(u"<"_s).isValid(), false);
|
||||
QCOMPARE(Path(u">"_s).isValid(), false);
|
||||
QCOMPARE(Path(u"|"_s).isValid(), false);
|
||||
|
||||
QCOMPARE(Path(uR"(\\nas01\drive)"_s).isValid(), true);
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\xxx)"_s).isValid(), true);
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\xxx\\)"_s).isValid(), true);
|
||||
#else
|
||||
QCOMPARE(Path(u"\0"_s).isValid(), false);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
void testIsUNCPath() const
|
||||
{
|
||||
QCOMPARE(Path(uR"(\\)"_s).isUNCPath, false);
|
||||
QCOMPARE(Path(uR"(\\\)"_s).isUNCPath, false);
|
||||
|
||||
QCOMPARE(Path(uR"(\\nas01\drive)"_s).isUNCPath, true);
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\)"_s).isUNCPath, true);
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\xxx)"_s).isUNCPath, true);
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\xxx\)"_s).isUNCPath, true);
|
||||
|
||||
QCOMPARE(Path(uR"(\\C:\\drive\xxx)"_s).isUNCPath, false);
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\?)"_s).isUNCPath, false);
|
||||
QCOMPARE(Path(uR"(\\nas01\?\xxx)"_s).isUNCPath, false);
|
||||
}
|
||||
#endif
|
||||
void testIsEmpty() const
|
||||
{
|
||||
QCOMPARE(Path().isEmpty(), true);
|
||||
|
@ -247,6 +271,10 @@ private slots:
|
|||
QCOMPARE(Path(uR"(c:\)"_s).rootItem(), Path(uR"(c:/)"_s));
|
||||
QCOMPARE(Path(uR"(c:\a)"_s).rootItem(), Path(uR"(c:\)"_s));
|
||||
QCOMPARE(Path(uR"(c:\a\b)"_s).rootItem(), Path(uR"(c:\)"_s));
|
||||
|
||||
QCOMPARE(Path(uR"(\\nas01\drive)"_s).rootItem(), Path(uR"(\\nas01\drive)"_s));
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\xxx)"_s).rootItem(), Path(uR"(\\nas01\drive)"_s));
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\xxx\yyy)"_s).rootItem(), Path(uR"(\\nas01\drive)"_s));
|
||||
#else
|
||||
QCOMPARE(Path(uR"(\a)"_s).rootItem(), Path(uR"(\a)"_s));
|
||||
QCOMPARE(Path(uR"(\\a)"_s).rootItem(), Path(uR"(\\a)"_s));
|
||||
|
@ -280,6 +308,10 @@ private slots:
|
|||
QCOMPARE(Path(uR"(c:\)"_s).parentPath(), Path());
|
||||
QCOMPARE(Path(uR"(c:\a)"_s).parentPath(), Path(uR"(c:\)"_s));
|
||||
QCOMPARE(Path(uR"(c:\a\b)"_s).parentPath(), Path(uR"(c:\a)"_s));
|
||||
|
||||
QCOMPARE(Path(uR"(\\nas01\drive)"_s).parentPath(), Path(uR"(\\nas01\drive)"_s));
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\xxx)"_s).parentPath(), Path(uR"(\\nas01\drive)"_s));
|
||||
QCOMPARE(Path(uR"(\\nas01\drive\xxx\yyy)"_s).parentPath(), Path(uR"(\\nas01\drive\xxx)"_s));
|
||||
#else
|
||||
QCOMPARE(Path(uR"(\a)"_s).parentPath(), Path());
|
||||
QCOMPARE(Path(uR"(\\a)"_s).parentPath(), Path());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue