WebAPI: Add a way to download .torrent file using search plugin
[qBittorrent.git] / src / base / utils / fs.cpp
blob4d89f49a00612af2fa9b666070c0e7520f9ce573
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 "fs.h"
32 #include <cerrno>
33 #include <cstring>
34 #include <filesystem>
36 #if defined(Q_OS_WIN)
37 #include <memory>
38 #endif
40 #include <sys/stat.h>
41 #include <sys/types.h>
43 #if defined(Q_OS_WIN)
44 #include <windows.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>
50 #else
51 #include <sys/vfs.h>
52 #include <unistd.h>
53 #endif
55 #include <QDateTime>
56 #include <QDebug>
57 #include <QDir>
58 #include <QDirIterator>
59 #include <QFile>
60 #include <QFileInfo>
61 #include <QRegularExpression>
62 #include <QStorageInfo>
64 #include "base/global.h"
65 #include "base/path.h"
67 /**
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)
78 if (!path.exists())
79 return true;
81 const QStringList deleteFilesList =
83 // Windows
84 u"Thumbs.db"_s,
85 u"desktop.ini"_s,
86 // Linux
87 u".directory"_s,
88 // Mac OS
89 u".DS_Store"_s
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))
103 const QDir dir {p};
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))
107 continue;
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));
117 if (hasOtherFiles)
118 continue;
120 for (const QString &f : tmpFileList)
121 removeFile(Path(p + f));
123 // remove directory if empty
124 dir.rmdir(p);
127 return path.exists();
131 * Removes directory and its content recursively.
133 void Utils::Fs::removeDirRecursively(const Path &path)
135 if (!path.isEmpty())
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
153 qint64 size = 0;
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();
160 return 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())
175 return false;
176 if (path1 == path2)
177 return true;
178 if (f1.size() != f2.size())
179 return false;
180 if (!f1.open(QIODevice::ReadOnly) || !f2.open(QIODevice::ReadOnly))
181 return false;
183 const int readSize = 1024 * 1024; // 1 MiB
184 while (!f1.atEnd() && !f2.atEnd())
186 if (f1.read(readSize) != f2.read(readSize))
187 return false;
189 return true;
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);
199 return validName;
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);
220 mkdir(path);
221 return path;
224 bool Utils::Fs::isRegularFile(const Path &path)
226 std::error_code ec;
227 return std::filesystem::is_regular_file(path.toStdFsPath(), ec);
230 bool Utils::Fs::isNetworkFileSystem(const Path &path)
232 #if defined Q_OS_HAIKU
233 return false;
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)))
238 return false;
239 return (::GetDriveTypeW(volumePath.get()) == DRIVE_REMOTE);
240 #else
241 const QString file = (path.toString() + u"/.");
242 struct statfs buf {};
243 if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
244 return false;
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));
250 #else
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
283 return true;
284 default:
285 break;
288 return false;
289 #endif
290 #endif
293 bool Utils::Fs::copyFile(const Path &from, const Path &to)
295 if (!from.exists())
296 return false;
298 if (!mkpath(to.parentPath()))
299 return false;
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()))
317 return true;
319 QFile file {path.data()};
320 if (!file.exists())
321 return true;
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());