#include "core/Scanner.h"
#include <QFile>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
namespace whittle {
namespace {
bool statxNoFollow(const QByteArray& path, struct statx& stx) {
return ::statx(AT_FDCWD, path.constData(), AT_SYMLINK_NOFOLLOW,
STATX_TYPE | STATX_MODE | STATX_SIZE |
STATX_MTIME | STATX_NLINK, &stx) == 0;
}
std::int64_t mtimeNs(const struct statx& stx) {
return std::int64_t(stx.stx_mtime.tv_sec) * 1'000'000'000LL
+ stx.stx_mtime.tv_nsec;
}
}
QHash<QString, FileRecord> Scanner::run(const QStringList& roots,
const QSet<QString>& excluded,
bool isUsb, bool recursive,
const std::atomic<bool>& cancel,
const ProgressFn& onProgress) {
QHash<QString, FileRecord> out;
int folders = 0, files = 0;
qint64 bytes = 0;
for (const QString& root : roots) {
if (cancel.load(std::memory_order_relaxed)) break;
const QString base = isUsb ? root : QString();
walk(root, base, recursive, excluded, out, folders, files, bytes,
cancel, onProgress);
}
if (onProgress) onProgress(folders, files, bytes);
return out;
}
void Scanner::walk(const QString& dirPath, const QString& base, bool recursive,
const QSet<QString>& excluded,
QHash<QString, FileRecord>& out,
int& folders, int& files, qint64& bytes,
const std::atomic<bool>& cancel, const ProgressFn& onProgress) {
if (cancel.load(std::memory_order_relaxed)) return;
const QByteArray dirEnc = QFile::encodeName(dirPath);
DIR* d = ::opendir(dirEnc.constData());
if (!d) return;
if (dirPath != base) {
struct statx stx;
if (statxNoFollow(dirEnc, stx) && S_ISDIR(stx.stx_mode)) {
FileRecord rec;
rec.absPath = dirPath;
rec.relPath = dirPath.mid(base.length() + 1);
rec.isDir = true;
rec.mtimeNs = mtimeNs(stx);
out.insert(rec.relPath, rec);
if (++folders % 256 == 0 && onProgress) onProgress(folders, files, bytes);
}
}
struct dirent* ent;
while ((ent = ::readdir(d)) != nullptr) {
if (cancel.load(std::memory_order_relaxed)) break;
const char* n = ent->d_name;
if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
continue;
const QString childPath = dirPath + '/' + QFile::decodeName(n);
const QByteArray childEnc = QFile::encodeName(childPath);
struct statx stx;
if (!statxNoFollow(childEnc, stx)) continue;
const auto mode = stx.stx_mode;
if (S_ISLNK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode) ||
S_ISCHR(mode) || S_ISBLK(mode))
continue;
if (S_ISDIR(mode)) {
if (recursive && !excluded.contains(childPath))
walk(childPath, base, recursive, excluded, out,
folders, files, bytes, cancel, onProgress);
} else if (S_ISREG(mode)) {
if (stx.stx_nlink > 1) continue;
FileRecord rec;
rec.absPath = childPath;
rec.relPath = childPath.mid(base.length() + 1);
rec.size = std::int64_t(stx.stx_size);
rec.mtimeNs = mtimeNs(stx);
out.insert(rec.relPath, rec);
bytes += rec.size;
if (++files % 256 == 0 && onProgress) onProgress(folders, files, bytes);
}
}
::closedir(d);
}
}