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.
34 #include <QDataStream>
38 #include <QMimeDatabase>
39 #include <QRegularExpression>
42 const Qt::CaseSensitivity CASE_SENSITIVITY
= Qt::CaseInsensitive
;
44 const Qt::CaseSensitivity CASE_SENSITIVITY
= Qt::CaseSensitive
;
47 const int PATHLIST_TYPEID
= qRegisterMetaType
<PathList
>("PathList");
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
'\\');
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
77 const QRegularExpression regex
{QLatin1String("[:?\"*<>|]")};
78 #elif defined(Q_OS_MACOS)
79 const QRegularExpression regex
{QLatin1String("[\\0:]")};
81 const QRegularExpression regex
{QLatin1String("[\\0]")};
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('/'));
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)
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)
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()))
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
192 QString
Path::toString() const
194 return QDir::toNativeSeparators(m_pathStr
);
197 Path
&Path::operator/=(const Path
&other
)
199 *this = *this / other
;
203 Path
&Path::operator+=(const QString
&str
)
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())
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)
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
)
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)
250 if (rootFolder
.isEmpty())
251 rootFolder
.m_pathStr
= filePathElements
.at(0).toString();
252 else if (rootFolder
.m_pathStr
!= filePathElements
.at(0))
259 void Path::stripRootFolder(PathList
&filePaths
)
261 const Path commonRootFolder
= findRootFolder(filePaths
);
262 if (commonRootFolder
.isEmpty())
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
)
280 path
.m_pathStr
= pathStr
;
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
)
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
)
327 QDataStream
&operator>>(QDataStream
&in
, Path
&path
)
331 path
= Path(pathStr
);
335 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
336 std::size_t qHash(const Path
&key
, const std::size_t seed
)
338 uint
qHash(const Path
&key
, const uint seed
)
341 return ::qHash(key
.data(), seed
);