2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2022-2024 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.
39 #include <sys/types.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>
53 #include <QCoreApplication>
57 #include <QDirIterator>
60 #include <QRegularExpression>
61 #include <QStorageInfo>
63 #include "base/global.h"
64 #include "base/path.h"
67 * This function will first check if there are only system cache files, e.g. `Thumbs.db`,
68 * `.DS_Store` and/or only temp files that end with '~', e.g. `filename~`.
69 * If they are the only files it will try to remove them and delete the folder.
70 * This action will be performed for each subfolder starting from the deepest folder.
71 * There is an inherent race condition here. A file might appear after it is checked
72 * that only the above mentioned "useless" files exist but before the whole folder is removed.
73 * In this case, the folder will not be removed but the "useless" files will be deleted.
75 bool Utils::Fs::smartRemoveEmptyFolderTree(const Path
&path
)
80 const QStringList deleteFilesList
=
91 // travel from the deepest folder and remove anything unwanted on the way out.
92 QStringList
dirList(path
.data() + u
'/'); // get all sub directories paths
93 QDirIterator iter
{path
.data(), (QDir::AllDirs
| QDir::NoDotAndDotDot
), QDirIterator::Subdirectories
};
94 while (iter
.hasNext())
95 dirList
<< iter
.next() + u
'/';
96 // sort descending by directory depth
97 std::sort(dirList
.begin(), dirList
.end()
98 , [](const QString
&l
, const QString
&r
) { return l
.count(u
'/') > r
.count(u
'/'); });
100 for (const QString
&p
: asConst(dirList
))
103 // A deeper folder may have not been removed in the previous iteration
104 // so don't remove anything from this folder either.
105 if (!dir
.isEmpty(QDir::Dirs
| QDir::NoDotAndDotDot
))
108 const QStringList tmpFileList
= dir
.entryList(QDir::Files
);
110 // deleteFilesList contains unwanted files, usually created by the OS
111 // temp files on linux usually end with '~', e.g. `filename~`
112 const bool hasOtherFiles
= std::any_of(tmpFileList
.cbegin(), tmpFileList
.cend(), [&deleteFilesList
](const QString
&f
)
114 return (!f
.endsWith(u
'~') && !deleteFilesList
.contains(f
, Qt::CaseInsensitive
));
119 for (const QString
&f
: tmpFileList
)
120 removeFile(Path(p
+ f
));
122 // remove directory if empty
126 return path
.exists();
130 * Removes directory and its content recursively.
132 void Utils::Fs::removeDirRecursively(const Path
&path
)
135 QDir(path
.data()).removeRecursively();
139 * Returns the size of a file.
140 * If the file is a folder, it will compute its size based on its content.
142 * Returns -1 in case of error.
144 qint64
Utils::Fs::computePathSize(const Path
&path
)
146 // Check if it is a file
147 const QFileInfo fi
{path
.data()};
148 if (!fi
.exists()) return -1;
149 if (fi
.isFile()) return fi
.size();
151 // Compute folder size based on its content
153 QDirIterator iter
{path
.data(), (QDir::Files
| QDir::Hidden
| QDir::NoSymLinks
), QDirIterator::Subdirectories
};
154 while (iter
.hasNext())
156 const QFileInfo fileInfo
= iter
.nextFileInfo();
157 size
+= fileInfo
.size();
163 * Makes deep comparison of two files to make sure they are identical.
164 * The point is about the file contents. If the files do not exist then
165 * the paths refers to nothing and therefore we cannot say the files are same
166 * (because there are no files!)
168 bool Utils::Fs::sameFiles(const Path
&path1
, const Path
&path2
)
170 QFile f1
{path1
.data()};
171 QFile f2
{path2
.data()};
173 if (!f1
.exists() || !f2
.exists())
177 if (f1
.size() != f2
.size())
179 if (!f1
.open(QIODevice::ReadOnly
) || !f2
.open(QIODevice::ReadOnly
))
182 const int readSize
= 1024 * 1024; // 1 MiB
183 while (!f1
.atEnd() && !f2
.atEnd())
185 if (f1
.read(readSize
) != f2
.read(readSize
))
191 QString
Utils::Fs::toValidFileName(const QString
&name
, const QString
&pad
)
193 const QRegularExpression regex
{u
"[\\\\/:?\"*<>|]+"_s
};
195 QString validName
= name
.trimmed();
196 validName
.replace(regex
, pad
);
201 Path
Utils::Fs::toValidPath(const QString
&name
, const QString
&pad
)
203 const QRegularExpression regex
{u
"[:?\"*<>|]+"_s
};
205 QString validPathStr
= name
;
206 validPathStr
.replace(regex
, pad
);
208 return Path(validPathStr
);
211 qint64
Utils::Fs::freeDiskSpaceOnPath(const Path
&path
)
213 return QStorageInfo(path
.data()).bytesAvailable();
216 Path
Utils::Fs::tempPath()
218 static const Path path
= Path(QDir::tempPath()) / Path(u
".qBittorrent"_s
);
223 bool Utils::Fs::isRegularFile(const Path
&path
)
226 return std::filesystem::is_regular_file(path
.toStdFsPath(), ec
);
229 bool Utils::Fs::isNetworkFileSystem(const Path
&path
)
231 #if defined Q_OS_HAIKU
233 #elif defined(Q_OS_WIN)
234 const std::wstring pathW
= path
.toString().toStdWString();
235 auto volumePath
= std::make_unique
<wchar_t[]>(pathW
.length() + 1);
236 if (!::GetVolumePathNameW(pathW
.c_str(), volumePath
.get(), static_cast<DWORD
>(pathW
.length() + 1)))
238 return (::GetDriveTypeW(volumePath
.get()) == DRIVE_REMOTE
);
240 const QString file
= (path
.toString() + u
"/.");
241 struct statfs buf
{};
242 if (statfs(file
.toLocal8Bit().constData(), &buf
) != 0)
245 #if defined(Q_OS_OPENBSD)
246 return ((strncmp(buf
.f_fstypename
, "cifs", sizeof(buf
.f_fstypename
)) == 0)
247 || (strncmp(buf
.f_fstypename
, "nfs", sizeof(buf
.f_fstypename
)) == 0)
248 || (strncmp(buf
.f_fstypename
, "smbfs", sizeof(buf
.f_fstypename
)) == 0));
250 // Magic number reference:
251 // https://github.com/coreutils/coreutils/blob/master/src/stat.c
252 switch (static_cast<quint32
>(buf
.f_type
))
254 case 0x0000517B: // SMB
255 case 0x0000564C: // NCP
256 case 0x00006969: // NFS
257 case 0x00C36400: // CEPH
258 case 0x01161970: // GFS
259 case 0x013111A8: // IBRIX
260 case 0x0BD00BD0: // LUSTRE
261 case 0x19830326: // FHGFS
262 case 0x47504653: // GPFS
263 case 0x50495045: // PIPEFS
264 case 0x5346414F: // AFS
265 case 0x61636673: // ACFS
266 case 0x61756673: // AUFS
267 case 0x65735543: // FUSECTL
268 case 0x65735546: // FUSEBLK
269 case 0x6B414653: // KAFS
270 case 0x6E667364: // NFSD
271 case 0x73757245: // CODA
272 case 0x7461636F: // OCFS2
273 case 0x786F4256: // VBOXSF
274 case 0x794C7630: // OVERLAYFS
275 case 0x7C7C6673: // PRL_FS
276 case 0xA501FCF5: // VXFS
277 case 0xAAD7AAEA: // OVERLAYFS
278 case 0xBACBACBC: // VMHGFS
279 case 0xBEEFDEAD: // SNFS
280 case 0xFE534D42: // SMB2
281 case 0xFF534D42: // CIFS
292 bool Utils::Fs::copyFile(const Path
&from
, const Path
&to
)
297 if (!mkpath(to
.parentPath()))
300 return QFile::copy(from
.data(), to
.data());
303 bool Utils::Fs::renameFile(const Path
&from
, const Path
&to
)
305 return QFile::rename(from
.data(), to
.data());
309 * Removes the file with the given filePath.
311 * This function will try to fix the file permissions before removing it.
313 nonstd::expected
<void, QString
> Utils::Fs::removeFile(const Path
&path
)
315 QFile file
{path
.data()};
322 // Make sure we have read/write permissions
323 file
.setPermissions(file
.permissions() | QFile::ReadOwner
| QFile::WriteOwner
| QFile::ReadUser
| QFile::WriteUser
);
327 return nonstd::make_unexpected(file
.errorString());
330 nonstd::expected
<void, QString
> Utils::Fs::moveFileToTrash(const Path
&path
)
332 QFile file
{path
.data()};
333 if (file
.moveToTrash())
339 // Make sure we have read/write permissions
340 file
.setPermissions(file
.permissions() | QFile::ReadOwner
| QFile::WriteOwner
| QFile::ReadUser
| QFile::WriteUser
);
341 if (file
.moveToTrash())
344 const QString errorMessage
= file
.errorString();
345 return nonstd::make_unexpected(!errorMessage
.isEmpty() ? errorMessage
: QCoreApplication::translate("fs", "Unknown error"));
349 bool Utils::Fs::isReadable(const Path
&path
)
351 return QFileInfo(path
.data()).isReadable();
354 bool Utils::Fs::isWritable(const Path
&path
)
356 return QFileInfo(path
.data()).isWritable();
359 QDateTime
Utils::Fs::lastModified(const Path
&path
)
361 return QFileInfo(path
.data()).lastModified();
364 bool Utils::Fs::isDir(const Path
&path
)
366 return QFileInfo(path
.data()).isDir();
369 Path
Utils::Fs::toAbsolutePath(const Path
&path
)
371 return Path(QFileInfo(path
.data()).absoluteFilePath());
374 Path
Utils::Fs::toCanonicalPath(const Path
&path
)
376 return Path(QFileInfo(path
.data()).canonicalFilePath());
379 Path
Utils::Fs::homePath()
381 return Path(QDir::homePath());
384 bool Utils::Fs::mkdir(const Path
&dirPath
)
386 return QDir().mkdir(dirPath
.data());
389 bool Utils::Fs::mkpath(const Path
&dirPath
)
391 return QDir().mkpath(dirPath
.data());
394 bool Utils::Fs::rmdir(const Path
&dirPath
)
396 return QDir().rmdir(dirPath
.data());