qBittorrent
fs.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  * In addition, as a special exception, the copyright holders give permission to
20  * link this program with the OpenSSL project's "OpenSSL" library (or with
21  * modified versions of it that use the same license as the "OpenSSL" library),
22  * and distribute the linked executables. You must obey the GNU General Public
23  * License in all respects for all of the code used other than "OpenSSL". If you
24  * modify file(s), you may extend this exception to your version of the file(s),
25  * but you are not obligated to do so. If you do not wish to do so, delete this
26  * exception statement from your version.
27  */
28 
29 #include "fs.h"
30 
31 #include <cerrno>
32 #include <cstring>
33 
34 #if defined(Q_OS_WIN)
35 #include <memory>
36 #endif
37 
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 
41 #if defined(Q_OS_WIN)
42 #include <Windows.h>
43 #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
44 #include <sys/param.h>
45 #include <sys/mount.h>
46 #elif defined(Q_OS_HAIKU)
47 #include <kernel/fs_info.h>
48 #else
49 #include <sys/vfs.h>
50 #include <unistd.h>
51 #endif
52 
53 #include <QDebug>
54 #include <QDir>
55 #include <QDirIterator>
56 #include <QFile>
57 #include <QFileInfo>
58 #include <QMimeDatabase>
59 #include <QStorageInfo>
60 #include <QRegularExpression>
61 
62 #include "base/global.h"
63 
64 QString Utils::Fs::toNativePath(const QString &path)
65 {
66  return QDir::toNativeSeparators(path);
67 }
68 
69 QString Utils::Fs::toUniformPath(const QString &path)
70 {
71  return QDir::fromNativeSeparators(path);
72 }
73 
74 QString Utils::Fs::resolvePath(const QString &relativePath, const QString &basePath)
75 {
76  Q_ASSERT(QDir::isRelativePath(relativePath));
77  Q_ASSERT(QDir::isAbsolutePath(basePath));
78 
79  return (relativePath.isEmpty() ? basePath : QDir(basePath).absoluteFilePath(relativePath));
80 }
81 
82 QString Utils::Fs::fileExtension(const QString &filename)
83 {
84  return QMimeDatabase().suffixForFileName(filename);
85 }
86 
87 QString Utils::Fs::fileName(const QString &filePath)
88 {
89  const QString path = toUniformPath(filePath);
90  const int slashIndex = path.lastIndexOf('/');
91  if (slashIndex == -1)
92  return path;
93  return path.mid(slashIndex + 1);
94 }
95 
96 QString Utils::Fs::folderName(const QString &filePath)
97 {
98  const QString path = toUniformPath(filePath);
99  const int slashIndex = path.lastIndexOf('/');
100  if (slashIndex == -1)
101  return {};
102  return path.left(slashIndex);
103 }
104 
114 bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
115 {
116  if (path.isEmpty() || !QDir(path).exists())
117  return true;
118 
119  const QStringList deleteFilesList =
120  {
121  // Windows
122  QLatin1String("Thumbs.db"),
123  QLatin1String("desktop.ini"),
124  // Linux
125  QLatin1String(".directory"),
126  // Mac OS
127  QLatin1String(".DS_Store")
128  };
129 
130  // travel from the deepest folder and remove anything unwanted on the way out.
131  QStringList dirList(path + '/'); // get all sub directories paths
132  QDirIterator iter(path, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories);
133  while (iter.hasNext())
134  dirList << iter.next() + '/';
135  // sort descending by directory depth
136  std::sort(dirList.begin(), dirList.end()
137  , [](const QString &l, const QString &r) { return l.count('/') > r.count('/'); });
138 
139  for (const QString &p : asConst(dirList))
140  {
141  const QDir dir(p);
142  // A deeper folder may have not been removed in the previous iteration
143  // so don't remove anything from this folder either.
144  if (!dir.isEmpty(QDir::Dirs | QDir::NoDotAndDotDot))
145  continue;
146 
147  const QStringList tmpFileList = dir.entryList(QDir::Files);
148 
149  // deleteFilesList contains unwanted files, usually created by the OS
150  // temp files on linux usually end with '~', e.g. `filename~`
151  const bool hasOtherFiles = std::any_of(tmpFileList.cbegin(), tmpFileList.cend(), [&deleteFilesList](const QString &f)
152  {
153  return (!f.endsWith('~') && !deleteFilesList.contains(f, Qt::CaseInsensitive));
154  });
155  if (hasOtherFiles)
156  continue;
157 
158  for (const QString &f : tmpFileList)
159  forceRemove(p + f);
160 
161  // remove directory if empty
162  dir.rmdir(p);
163  }
164 
165  return QDir(path).exists();
166 }
167 
173 bool Utils::Fs::forceRemove(const QString &filePath)
174 {
175  QFile f(filePath);
176  if (!f.exists())
177  return true;
178  // Make sure we have read/write permissions
179  f.setPermissions(f.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
180  // Remove the file
181  return f.remove();
182 }
183 
187 void Utils::Fs::removeDirRecursive(const QString &path)
188 {
189  if (!path.isEmpty())
190  QDir(path).removeRecursively();
191 }
192 
199 qint64 Utils::Fs::computePathSize(const QString &path)
200 {
201  // Check if it is a file
202  const QFileInfo fi(path);
203  if (!fi.exists()) return -1;
204  if (fi.isFile()) return fi.size();
205 
206  // Compute folder size based on its content
207  qint64 size = 0;
208  QDirIterator iter(path, QDir::Files | QDir::Hidden | QDir::NoSymLinks, QDirIterator::Subdirectories);
209  while (iter.hasNext())
210  {
211  iter.next();
212  size += iter.fileInfo().size();
213  }
214  return size;
215 }
216 
220 bool Utils::Fs::sameFiles(const QString &path1, const QString &path2)
221 {
222  QFile f1(path1), f2(path2);
223  if (!f1.exists() || !f2.exists()) return false;
224  if (f1.size() != f2.size()) return false;
225  if (!f1.open(QIODevice::ReadOnly)) return false;
226  if (!f2.open(QIODevice::ReadOnly)) return false;
227 
228  const int readSize = 1024 * 1024; // 1 MiB
229  while (!f1.atEnd() && !f2.atEnd())
230  {
231  if (f1.read(readSize) != f2.read(readSize))
232  return false;
233  }
234  return true;
235 }
236 
237 QString Utils::Fs::toValidFileSystemName(const QString &name, const bool allowSeparators, const QString &pad)
238 {
239  const QRegularExpression regex(allowSeparators ? "[:?\"*<>|]+" : "[\\\\/:?\"*<>|]+");
240 
241  QString validName = name.trimmed();
242  validName.replace(regex, pad);
243  qDebug() << "toValidFileSystemName:" << name << "=>" << validName;
244 
245  return validName;
246 }
247 
248 bool Utils::Fs::isValidFileSystemName(const QString &name, const bool allowSeparators)
249 {
250  if (name.isEmpty()) return false;
251 
252 #if defined(Q_OS_WIN)
253  const QRegularExpression regex
254  {allowSeparators
255  ? QLatin1String("[:?\"*<>|]")
256  : QLatin1String("[\\\\/:?\"*<>|]")};
257 #elif defined(Q_OS_MACOS)
258  const QRegularExpression regex
259  {allowSeparators
260  ? QLatin1String("[\\0:]")
261  : QLatin1String("[\\0/:]")};
262 #else
263  const QRegularExpression regex
264  {allowSeparators
265  ? QLatin1String("[\\0]")
266  : QLatin1String("[\\0/]")};
267 #endif
268  return !name.contains(regex);
269 }
270 
271 qint64 Utils::Fs::freeDiskSpaceOnPath(const QString &path)
272 {
273  return QStorageInfo(path).bytesAvailable();
274 }
275 
276 QString Utils::Fs::branchPath(const QString &filePath, QString *removed)
277 {
278  QString ret = toUniformPath(filePath);
279  if (ret.endsWith('/'))
280  ret.chop(1);
281  const int slashIndex = ret.lastIndexOf('/');
282  if (slashIndex >= 0)
283  {
284  if (removed)
285  *removed = ret.mid(slashIndex + 1);
286  ret = ret.left(slashIndex);
287  }
288  return ret;
289 }
290 
291 bool Utils::Fs::sameFileNames(const QString &first, const QString &second)
292 {
293 #if defined(Q_OS_UNIX) || defined(Q_WS_QWS)
294  return QString::compare(first, second, Qt::CaseSensitive) == 0;
295 #else
296  return QString::compare(first, second, Qt::CaseInsensitive) == 0;
297 #endif
298 }
299 
300 QString Utils::Fs::expandPath(const QString &path)
301 {
302  const QString ret = path.trimmed();
303  if (ret.isEmpty())
304  return ret;
305 
306  return QDir::cleanPath(ret);
307 }
308 
309 QString Utils::Fs::expandPathAbs(const QString &path)
310 {
311  return QDir(expandPath(path)).absolutePath();
312 }
313 
315 {
316  static const QString path = QDir::tempPath() + "/.qBittorrent/";
317  QDir().mkdir(path);
318  return path;
319 }
320 
321 bool Utils::Fs::isRegularFile(const QString &path)
322 {
323  struct ::stat st;
324  if (::stat(path.toUtf8().constData(), &st) != 0)
325  {
326  // analyse erno and log the error
327  const auto err = errno;
328  qDebug("Could not get file stats for path '%s'. Error: %s"
329  , qUtf8Printable(path), qUtf8Printable(strerror(err)));
330  return false;
331  }
332 
333  return (st.st_mode & S_IFMT) == S_IFREG;
334 }
335 
336 #if !defined Q_OS_HAIKU
337 bool Utils::Fs::isNetworkFileSystem(const QString &path)
338 {
339 #if defined(Q_OS_WIN)
340  const std::wstring pathW {path.toStdWString()};
341  auto volumePath = std::make_unique<wchar_t[]>(path.length() + 1);
342  if (!::GetVolumePathNameW(pathW.c_str(), volumePath.get(), (path.length() + 1)))
343  return false;
344  return (::GetDriveTypeW(volumePath.get()) == DRIVE_REMOTE);
345 #else
346  QString file = path;
347  if (!file.endsWith('/'))
348  file += '/';
349  file += '.';
350 
351  struct statfs buf {};
352  if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
353  return false;
354 
355 #if defined(Q_OS_OPENBSD)
356  return ((strncmp(buf.f_fstypename, "cifs", sizeof(buf.f_fstypename)) == 0)
357  || (strncmp(buf.f_fstypename, "nfs", sizeof(buf.f_fstypename)) == 0)
358  || (strncmp(buf.f_fstypename, "smbfs", sizeof(buf.f_fstypename)) == 0));
359 #else
360  // Magic number reference:
361  // https://github.com/coreutils/coreutils/blob/master/src/stat.c
362  switch (static_cast<quint32>(buf.f_type))
363  {
364  case 0x0000517B: // SMB
365  case 0x0000564C: // NCP
366  case 0x00006969: // NFS
367  case 0x00C36400: // CEPH
368  case 0x01161970: // GFS
369  case 0x013111A8: // IBRIX
370  case 0x0BD00BD0: // LUSTRE
371  case 0x19830326: // FHGFS
372  case 0x47504653: // GPFS
373  case 0x50495045: // PIPEFS
374  case 0x5346414F: // AFS
375  case 0x61636673: // ACFS
376  case 0x61756673: // AUFS
377  case 0x65735543: // FUSECTL
378  case 0x65735546: // FUSEBLK
379  case 0x6B414653: // KAFS
380  case 0x6E667364: // NFSD
381  case 0x73757245: // CODA
382  case 0x7461636F: // OCFS2
383  case 0x786F4256: // VBOXSF
384  case 0x794C7630: // OVERLAYFS
385  case 0x7C7C6673: // PRL_FS
386  case 0xA501FCF5: // VXFS
387  case 0xAAD7AAEA: // OVERLAYFS
388  case 0xBACBACBC: // VMHGFS
389  case 0xBEEFDEAD: // SNFS
390  case 0xFE534D42: // SMB2
391  case 0xFF534D42: // CIFS
392  return true;
393  default:
394  break;
395  }
396 
397  return false;
398 #endif
399 #endif
400 }
401 #endif // Q_OS_HAIKU
402 
403 QString Utils::Fs::findRootFolder(const QStringList &filePaths)
404 {
405  QString rootFolder;
406  for (const QString &filePath : filePaths)
407  {
408  const auto filePathElements = QStringView(filePath).split(u'/');
409  // if at least one file has no root folder, no common root folder exists
410  if (filePathElements.count() <= 1)
411  return {};
412 
413  if (rootFolder.isEmpty())
414  rootFolder = filePathElements.at(0).toString();
415  else if (rootFolder != filePathElements.at(0))
416  return {};
417  }
418 
419  return rootFolder;
420 }
421 
422 void Utils::Fs::stripRootFolder(QStringList &filePaths)
423 {
424  const QString commonRootFolder = findRootFolder(filePaths);
425  if (commonRootFolder.isEmpty())
426  return;
427 
428  for (QString &filePath : filePaths)
429  filePath = filePath.mid(commonRootFolder.size() + 1);
430 }
431 
432 void Utils::Fs::addRootFolder(QStringList &filePaths, const QString &rootFolder)
433 {
434  Q_ASSERT(!rootFolder.isEmpty());
435 
436  for (QString &filePath : filePaths)
437  filePath = rootFolder + QLatin1Char('/') + filePath;
438 }
void f1(struct fred_t *p)
Definition: clang.c:1
void f2()
Definition: clang.c:10
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
bool isRegularFile(const QString &path)
Definition: fs.cpp:321
bool sameFiles(const QString &path1, const QString &path2)
Definition: fs.cpp:220
bool isNetworkFileSystem(const QString &path)
Definition: fs.cpp:337
qint64 freeDiskSpaceOnPath(const QString &path)
Definition: fs.cpp:271
QString folderName(const QString &filePath)
Definition: fs.cpp:96
bool sameFileNames(const QString &first, const QString &second)
Definition: fs.cpp:291
QString fileName(const QString &filePath)
Definition: fs.cpp:87
QString toValidFileSystemName(const QString &name, bool allowSeparators=false, const QString &pad=QLatin1String(" "))
Definition: fs.cpp:237
void stripRootFolder(QStringList &filePaths)
Definition: fs.cpp:422
QString resolvePath(const QString &relativePath, const QString &basePath)
Definition: fs.cpp:74
void removeDirRecursive(const QString &path)
Definition: fs.cpp:187
qint64 computePathSize(const QString &path)
Definition: fs.cpp:199
QString fileExtension(const QString &filename)
Definition: fs.cpp:82
QString findRootFolder(const QStringList &filePaths)
Definition: fs.cpp:403
void addRootFolder(QStringList &filePaths, const QString &name)
Definition: fs.cpp:432
bool isValidFileSystemName(const QString &name, bool allowSeparators=false)
Definition: fs.cpp:248
QString tempPath()
Definition: fs.cpp:314
QString toUniformPath(const QString &path)
Definition: fs.cpp:69
QString branchPath(const QString &filePath, QString *removed=nullptr)
Definition: fs.cpp:276
QString expandPathAbs(const QString &path)
Definition: fs.cpp:309
bool smartRemoveEmptyFolderTree(const QString &path)
Definition: fs.cpp:114
bool forceRemove(const QString &filePath)
Definition: fs.cpp:173
QString expandPath(const QString &path)
Definition: fs.cpp:300
QString toNativePath(const QString &path)
Definition: fs.cpp:64
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") set_source_files_properties($
Definition: CMakeLists.txt:5
void f()
Definition: test2.c:1