Sync translations from Transifex and run lupdate
[qBittorrent.git] / src / base / path.cpp
bloba7675fb6803cc5963286b0f8ac7fdd5ab1082cf8
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "path.h"
32 #include <algorithm>
34 #include <QDataStream>
35 #include <QDir>
36 #include <QFileInfo>
37 #include <QList>
38 #include <QMimeDatabase>
39 #include <QRegularExpression>
41 #if defined(Q_OS_WIN)
42 const Qt::CaseSensitivity CASE_SENSITIVITY = Qt::CaseInsensitive;
43 #else
44 const Qt::CaseSensitivity CASE_SENSITIVITY = Qt::CaseSensitive;
45 #endif
47 const int PATHLIST_TYPEID = qRegisterMetaType<PathList>("PathList");
49 namespace
51 QString cleanPath(const QString &path)
53 const bool hasSeparator = std::any_of(path.cbegin(), path.cend(), [](const QChar c)
55 return (c == u'/') || (c == u'\\');
56 });
57 return hasSeparator ? QDir::cleanPath(path) : path;
61 Path::Path(const QString &pathStr)
62 : m_pathStr {cleanPath(pathStr)}
66 Path::Path(const std::string &pathStr)
67 : Path(QString::fromStdString(pathStr))
71 bool Path::isValid() const
73 if (isEmpty())
74 return false;
76 #if defined(Q_OS_WIN)
77 const QRegularExpression regex {QLatin1String("[:?\"*<>|]")};
78 #elif defined(Q_OS_MACOS)
79 const QRegularExpression regex {QLatin1String("[\\0:]")};
80 #else
81 const QRegularExpression regex {QLatin1String("[\\0]")};
82 #endif
83 return !m_pathStr.contains(regex);
86 bool Path::isEmpty() const
88 return m_pathStr.isEmpty();
91 bool Path::isAbsolute() const
93 return QDir::isAbsolutePath(m_pathStr);
96 bool Path::isRelative() const
98 return QDir::isRelativePath(m_pathStr);
101 bool Path::exists() const
103 return !isEmpty() && QFileInfo::exists(m_pathStr);
106 Path Path::rootItem() const
108 const int slashIndex = m_pathStr.indexOf(QLatin1Char('/'));
109 if (slashIndex < 0)
110 return *this;
112 if (slashIndex == 0) // *nix absolute path
113 return createUnchecked(QLatin1String("/"));
115 return createUnchecked(m_pathStr.left(slashIndex));
118 Path Path::parentPath() const
120 const int slashIndex = m_pathStr.lastIndexOf(QLatin1Char('/'));
121 if (slashIndex == -1)
122 return {};
124 if (slashIndex == 0) // *nix absolute path
125 return (m_pathStr.size() == 1) ? Path() : createUnchecked(QLatin1String("/"));
127 return createUnchecked(m_pathStr.left(slashIndex));
130 QString Path::filename() const
132 const int slashIndex = m_pathStr.lastIndexOf(u'/');
133 if (slashIndex == -1)
134 return m_pathStr;
136 return m_pathStr.mid(slashIndex + 1);
139 QString Path::extension() const
141 const QString suffix = QMimeDatabase().suffixForFileName(m_pathStr);
142 if (!suffix.isEmpty())
143 return (QLatin1String(".") + suffix);
145 const int slashIndex = m_pathStr.lastIndexOf(QLatin1Char('/'));
146 const auto filename = QStringView(m_pathStr).mid(slashIndex + 1);
147 const int dotIndex = filename.lastIndexOf(QLatin1Char('.'), -2);
148 return ((dotIndex == -1) ? QString() : filename.mid(dotIndex).toString());
151 bool Path::hasExtension(const QString &ext) const
153 Q_ASSERT(ext.startsWith(QLatin1Char('.')) && (ext.size() >= 2));
155 return m_pathStr.endsWith(ext, Qt::CaseInsensitive);
158 bool Path::hasAncestor(const Path &other) const
160 if (other.isEmpty() || (m_pathStr.size() <= other.m_pathStr.size()))
161 return false;
163 return (m_pathStr[other.m_pathStr.size()] == QLatin1Char('/'))
164 && m_pathStr.startsWith(other.m_pathStr, CASE_SENSITIVITY);
167 Path Path::relativePathOf(const Path &childPath) const
169 // If both paths are relative, we assume that they have the same base path
170 if (isRelative() && childPath.isRelative())
171 return Path(QDir(QDir::home().absoluteFilePath(m_pathStr)).relativeFilePath(QDir::home().absoluteFilePath(childPath.data())));
173 return Path(QDir(m_pathStr).relativeFilePath(childPath.data()));
176 void Path::removeExtension()
178 m_pathStr.chop(extension().size());
181 void Path::removeExtension(const QString &ext)
183 if (hasExtension(ext))
184 m_pathStr.chop(ext.size());
187 QString Path::data() const
189 return m_pathStr;
192 QString Path::toString() const
194 return QDir::toNativeSeparators(m_pathStr);
197 Path &Path::operator/=(const Path &other)
199 *this = *this / other;
200 return *this;
203 Path &Path::operator+=(const QString &str)
205 *this = *this + str;
206 return *this;
209 Path &Path::operator+=(const std::string &str)
211 return (*this += QString::fromStdString(str));
214 Path Path::commonPath(const Path &left, const Path &right)
216 if (left.isEmpty() || right.isEmpty())
217 return {};
219 const QList<QStringView> leftPathItems = QStringView(left.m_pathStr).split(u'/');
220 const QList<QStringView> rightPathItems = QStringView(right.m_pathStr).split(u'/');
221 int commonItemsCount = 0;
222 qsizetype commonPathSize = 0;
223 while ((commonItemsCount < leftPathItems.size()) && (commonItemsCount < rightPathItems.size()))
225 const QStringView leftPathItem = leftPathItems[commonItemsCount];
226 const QStringView rightPathItem = rightPathItems[commonItemsCount];
227 if (leftPathItem.compare(rightPathItem, CASE_SENSITIVITY) != 0)
228 break;
230 ++commonItemsCount;
231 commonPathSize += leftPathItem.size();
234 if (commonItemsCount > 0)
235 commonPathSize += (commonItemsCount - 1); // size of intermediate separators
237 return Path::createUnchecked(left.m_pathStr.left(commonPathSize));
240 Path Path::findRootFolder(const PathList &filePaths)
242 Path rootFolder;
243 for (const Path &filePath : filePaths)
245 const auto filePathElements = QStringView(filePath.m_pathStr).split(u'/');
246 // if at least one file has no root folder, no common root folder exists
247 if (filePathElements.count() <= 1)
248 return {};
250 if (rootFolder.isEmpty())
251 rootFolder.m_pathStr = filePathElements.at(0).toString();
252 else if (rootFolder.m_pathStr != filePathElements.at(0))
253 return {};
256 return rootFolder;
259 void Path::stripRootFolder(PathList &filePaths)
261 const Path commonRootFolder = findRootFolder(filePaths);
262 if (commonRootFolder.isEmpty())
263 return;
265 for (Path &filePath : filePaths)
266 filePath.m_pathStr = filePath.m_pathStr.mid(commonRootFolder.m_pathStr.size() + 1);
269 void Path::addRootFolder(PathList &filePaths, const Path &rootFolder)
271 Q_ASSERT(!rootFolder.isEmpty());
273 for (Path &filePath : filePaths)
274 filePath = rootFolder / filePath;
277 Path Path::createUnchecked(const QString &pathStr)
279 Path path;
280 path.m_pathStr = pathStr;
282 return path;
285 bool operator==(const Path &lhs, const Path &rhs)
287 return (lhs.data().compare(rhs.data(), CASE_SENSITIVITY) == 0);
290 bool operator!=(const Path &lhs, const Path &rhs)
292 return !(lhs == rhs);
295 Path operator/(const Path &lhs, const Path &rhs)
297 if (rhs.isEmpty())
298 return lhs;
300 if (lhs.isEmpty())
301 return rhs;
303 return Path(lhs.m_pathStr + QLatin1Char('/') + rhs.m_pathStr);
306 Path operator+(const Path &lhs, const QString &rhs)
308 return Path(lhs.m_pathStr + rhs);
311 Path operator+(const Path &lhs, const char rhs[])
313 return lhs + QString::fromLatin1(rhs);
316 Path operator+(const Path &lhs, const std::string &rhs)
318 return lhs + QString::fromStdString(rhs);
321 QDataStream &operator<<(QDataStream &out, const Path &path)
323 out << path.data();
324 return out;
327 QDataStream &operator>>(QDataStream &in, Path &path)
329 QString pathStr;
330 in >> pathStr;
331 path = Path(pathStr);
332 return in;
335 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
336 std::size_t qHash(const Path &key, const std::size_t seed)
337 #else
338 uint qHash(const Path &key, const uint seed)
339 #endif
341 return ::qHash(key.data(), seed);