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.
41 #include <sys/types.h>
45 #elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
46 #include <sys/param.h>
47 #include <sys/mount.h>
48 #elif defined(Q_OS_HAIKU)
49 #include <kernel/fs_info.h>
58 #include <QDirIterator>
61 #include <QRegularExpression>
62 #include <QStorageInfo>
64 #include "base/global.h"
65 #include "base/path.h"
68 * This function will first check if there are only system cache files, e.g. `Thumbs.db`,
69 * `.DS_Store` and/or only temp files that end with '~', e.g. `filename~`.
70 * If they are the only files it will try to remove them and delete the folder.
71 * This action will be performed for each subfolder starting from the deepest folder.
72 * There is an inherent race condition here. A file might appear after it is checked
73 * that only the above mentioned "useless" files exist but before the whole folder is removed.
74 * In this case, the folder will not be removed but the "useless" files will be deleted.
76 bool Utils::Fs::smartRemoveEmptyFolderTree(const Path
&path
)
81 const QStringList deleteFilesList
=
92 // travel from the deepest folder and remove anything unwanted on the way out.
93 QStringList
dirList(path
.data() + u
'/'); // get all sub directories paths
94 QDirIterator iter
{path
.data(), (QDir::AllDirs
| QDir::NoDotAndDotDot
), QDirIterator::Subdirectories
};
95 while (iter
.hasNext())
96 dirList
<< iter
.next() + u
'/';
97 // sort descending by directory depth
98 std::sort(dirList
.begin(), dirList
.end()
99 , [](const QString
&l
, const QString
&r
) { return l
.count(u
'/') > r
.count(u
'/'); });
101 for (const QString
&p
: asConst(dirList
))
104 // A deeper folder may have not been removed in the previous iteration
105 // so don't remove anything from this folder either.
106 if (!dir
.isEmpty(QDir::Dirs
| QDir::NoDotAndDotDot
))
109 const QStringList tmpFileList
= dir
.entryList(QDir::Files
);
111 // deleteFilesList contains unwanted files, usually created by the OS
112 // temp files on linux usually end with '~', e.g. `filename~`
113 const bool hasOtherFiles
= std::any_of(tmpFileList
.cbegin(), tmpFileList
.cend(), [&deleteFilesList
](const QString
&f
)
115 return (!f
.endsWith(u
'~') && !deleteFilesList
.contains(f
, Qt::CaseInsensitive
));
120 for (const QString
&f
: tmpFileList
)
121 removeFile(Path(p
+ f
));
123 // remove directory if empty
127 return path
.exists();
131 * Removes directory and its content recursively.
133 void Utils::Fs::removeDirRecursively(const Path
&path
)
136 QDir(path
.data()).removeRecursively();
140 * Returns the size of a file.
141 * If the file is a folder, it will compute its size based on its content.
143 * Returns -1 in case of error.
145 qint64
Utils::Fs::computePathSize(const Path
&path
)
147 // Check if it is a file
148 const QFileInfo fi
{path
.data()};
149 if (!fi
.exists()) return -1;
150 if (fi
.isFile()) return fi
.size();
152 // Compute folder size based on its content
154 QDirIterator iter
{path
.data(), (QDir::Files
| QDir::Hidden
| QDir::NoSymLinks
), QDirIterator::Subdirectories
};
155 while (iter
.hasNext())
157 const QFileInfo fileInfo
= iter
.nextFileInfo();
158 size
+= fileInfo
.size();
164 * Makes deep comparison of two files to make sure they are identical.
165 * The point is about the file contents. If the files do not exist then
166 * the paths refers to nothing and therefore we cannot say the files are same
167 * (because there are no files!)
169 bool Utils::Fs::sameFiles(const Path
&path1
, const Path
&path2
)
171 QFile f1
{path1
.data()};
172 QFile f2
{path2
.data()};
174 if (!f1
.exists() || !f2
.exists())
178 if (f1
.size() != f2
.size())
180 if (!f1
.open(QIODevice::ReadOnly
) || !f2
.open(QIODevice::ReadOnly
))
183 const int readSize
= 1024 * 1024; // 1 MiB
184 while (!f1
.atEnd() && !f2
.atEnd())
186 if (f1
.read(readSize
) != f2
.read(readSize
))
192 QString
Utils::Fs::toValidFileName(const QString
&name
, const QString
&pad
)
194 const QRegularExpression regex
{u
"[\\\\/:?\"*<>|]+"_s
};
196 QString validName
= name
.trimmed();
197 validName
.replace(regex
, pad
);
202 Path
Utils::Fs::toValidPath(const QString
&name
, const QString
&pad
)
204 const QRegularExpression regex
{u
"[:?\"*<>|]+"_s
};
206 QString validPathStr
= name
;
207 validPathStr
.replace(regex
, pad
);
209 return Path(validPathStr
);
212 qint64
Utils::Fs::freeDiskSpaceOnPath(const Path
&path
)
214 return QStorageInfo(path
.data()).bytesAvailable();
217 Path
Utils::Fs::tempPath()
219 static const Path path
= Path(QDir::tempPath()) / Path(u
".qBittorrent"_s
);
224 bool Utils::Fs::isRegularFile(const Path
&path
)
227 return std::filesystem::is_regular_file(path
.toStdFsPath(), ec
);
230 bool Utils::Fs::isNetworkFileSystem(const Path
&path
)
232 #if defined Q_OS_HAIKU
234 #elif defined(Q_OS_WIN)
235 const std::wstring pathW
= path
.toString().toStdWString();
236 auto volumePath
= std::make_unique
<wchar_t[]>(pathW
.length() + 1);
237 if (!::GetVolumePathNameW(pathW
.c_str(), volumePath
.get(), static_cast<DWORD
>(pathW
.length() + 1)))
239 return (::GetDriveTypeW(volumePath
.get()) == DRIVE_REMOTE
);
241 const QString file
= (path
.toString() + u
"/.");
242 struct statfs buf
{};
243 if (statfs(file
.toLocal8Bit().constData(), &buf
) != 0)
246 #if defined(Q_OS_OPENBSD)
247 return ((strncmp(buf
.f_fstypename
, "cifs", sizeof(buf
.f_fstypename
)) == 0)
248 || (strncmp(buf
.f_fstypename
, "nfs", sizeof(buf
.f_fstypename
)) == 0)
249 || (strncmp(buf
.f_fstypename
, "smbfs", sizeof(buf
.f_fstypename
)) == 0));
251 // Magic number reference:
252 // https://github.com/coreutils/coreutils/blob/master/src/stat.c
253 switch (static_cast<quint32
>(buf
.f_type
))
255 case 0x0000517B: // SMB
256 case 0x0000564C: // NCP
257 case 0x00006969: // NFS
258 case 0x00C36400: // CEPH
259 case 0x01161970: // GFS
260 case 0x013111A8: // IBRIX
261 case 0x0BD00BD0: // LUSTRE
262 case 0x19830326: // FHGFS
263 case 0x47504653: // GPFS
264 case 0x50495045: // PIPEFS
265 case 0x5346414F: // AFS
266 case 0x61636673: // ACFS
267 case 0x61756673: // AUFS
268 case 0x65735543: // FUSECTL
269 case 0x65735546: // FUSEBLK
270 case 0x6B414653: // KAFS
271 case 0x6E667364: // NFSD
272 case 0x73757245: // CODA
273 case 0x7461636F: // OCFS2
274 case 0x786F4256: // VBOXSF
275 case 0x794C7630: // OVERLAYFS
276 case 0x7C7C6673: // PRL_FS
277 case 0xA501FCF5: // VXFS
278 case 0xAAD7AAEA: // OVERLAYFS
279 case 0xBACBACBC: // VMHGFS
280 case 0xBEEFDEAD: // SNFS
281 case 0xFE534D42: // SMB2
282 case 0xFF534D42: // CIFS
293 bool Utils::Fs::copyFile(const Path
&from
, const Path
&to
)
298 if (!mkpath(to
.parentPath()))
301 return QFile::copy(from
.data(), to
.data());
304 bool Utils::Fs::renameFile(const Path
&from
, const Path
&to
)
306 return QFile::rename(from
.data(), to
.data());
310 * Removes the file with the given filePath.
312 * This function will try to fix the file permissions before removing it.
314 bool Utils::Fs::removeFile(const Path
&path
)
316 if (QFile::remove(path
.data()))
319 QFile file
{path
.data()};
323 // Make sure we have read/write permissions
324 file
.setPermissions(file
.permissions() | QFile::ReadOwner
| QFile::WriteOwner
| QFile::ReadUser
| QFile::WriteUser
);
325 return file
.remove();
328 bool Utils::Fs::isReadable(const Path
&path
)
330 return QFileInfo(path
.data()).isReadable();
333 bool Utils::Fs::isWritable(const Path
&path
)
335 return QFileInfo(path
.data()).isWritable();
338 QDateTime
Utils::Fs::lastModified(const Path
&path
)
340 return QFileInfo(path
.data()).lastModified();
343 bool Utils::Fs::isDir(const Path
&path
)
345 return QFileInfo(path
.data()).isDir();
348 Path
Utils::Fs::toAbsolutePath(const Path
&path
)
350 return Path(QFileInfo(path
.data()).absoluteFilePath());
353 Path
Utils::Fs::toCanonicalPath(const Path
&path
)
355 return Path(QFileInfo(path
.data()).canonicalFilePath());
358 Path
Utils::Fs::homePath()
360 return Path(QDir::homePath());
363 bool Utils::Fs::mkdir(const Path
&dirPath
)
365 return QDir().mkdir(dirPath
.data());
368 bool Utils::Fs::mkpath(const Path
&dirPath
)
370 return QDir().mkpath(dirPath
.data());
373 bool Utils::Fs::rmdir(const Path
&dirPath
)
375 return QDir().rmdir(dirPath
.data());