fix(search): make stop interrupt all phases
This commit is contained in:
+64
-12
@@ -255,6 +255,12 @@ public slots:
|
|||||||
void search(const SearchOptions &o)
|
void search(const SearchOptions &o)
|
||||||
{
|
{
|
||||||
cancelled_.store(false, std::memory_order_relaxed);
|
cancelled_.store(false, std::memory_order_relaxed);
|
||||||
|
const auto finishIfCancelled = [this] {
|
||||||
|
if (!cancelled_.load(std::memory_order_relaxed))
|
||||||
|
return false;
|
||||||
|
emit finished({}, tr("Search stopped"));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
cancelledByLimit_ = false;
|
cancelledByLimit_ = false;
|
||||||
cache_.setEnabled(o.useCache);
|
cache_.setEnabled(o.useCache);
|
||||||
cacheHits_.store(0, std::memory_order_relaxed);
|
cacheHits_.store(0, std::memory_order_relaxed);
|
||||||
@@ -284,7 +290,8 @@ public slots:
|
|||||||
dirOptions |= fs::directory_options::follow_directory_symlink;
|
dirOptions |= fs::directory_options::follow_directory_symlink;
|
||||||
if (!o.recursive) {
|
if (!o.recursive) {
|
||||||
for (fs::directory_iterator it(root, dirOptions, ec), end;
|
for (fs::directory_iterator it(root, dirOptions, ec), end;
|
||||||
it != end && !ec; it.increment(ec)) {
|
it != end && !ec && !cancelled_.load(std::memory_order_relaxed);
|
||||||
|
it.increment(ec)) {
|
||||||
++totalEntries;
|
++totalEntries;
|
||||||
if (totalEntries % 2048 == 0)
|
if (totalEntries % 2048 == 0)
|
||||||
emit phaseProgress(tr("Counting files"), totalEntries, 0);
|
emit phaseProgress(tr("Counting files"), totalEntries, 0);
|
||||||
@@ -397,7 +404,9 @@ public slots:
|
|||||||
|
|
||||||
if (!o.recursive) {
|
if (!o.recursive) {
|
||||||
for (fs::directory_iterator it(root, dirOptions, ec), end;
|
for (fs::directory_iterator it(root, dirOptions, ec), end;
|
||||||
it != end && !ec && !cancelledByLimit_; it.increment(ec)) {
|
it != end && !ec && !cancelled_.load(std::memory_order_relaxed)
|
||||||
|
&& !cancelledByLimit_;
|
||||||
|
it.increment(ec)) {
|
||||||
process(*it, root);
|
process(*it, root);
|
||||||
if (++scanned % 512 == 0) {
|
if (++scanned % 512 == 0) {
|
||||||
if (totalEntries)
|
if (totalEntries)
|
||||||
@@ -438,10 +447,8 @@ public slots:
|
|||||||
if (totalEntries)
|
if (totalEntries)
|
||||||
emit phaseProgress(tr("Scanning files"), std::min(scanned, totalEntries), totalEntries);
|
emit phaseProgress(tr("Scanning files"), std::min(scanned, totalEntries), totalEntries);
|
||||||
|
|
||||||
if (cancelled_.load(std::memory_order_relaxed)) {
|
if (finishIfCancelled())
|
||||||
emit finished({}, tr("Search stopped"));
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (!o.contains.isEmpty() && !o.findFolders) {
|
if (!o.contains.isEmpty() && !o.findFolders) {
|
||||||
emit phaseProgress(tr("Searching file contents"), 0, candidates.size());
|
emit phaseProgress(tr("Searching file contents"), 0, candidates.size());
|
||||||
@@ -449,6 +456,8 @@ public slots:
|
|||||||
const qsizetype total = candidates.size();
|
const qsizetype total = candidates.size();
|
||||||
QFuture<FileRecord> future = QtConcurrent::filtered(
|
QFuture<FileRecord> future = QtConcurrent::filtered(
|
||||||
candidates, [this, o, &checked, total](const FileRecord &r) {
|
candidates, [this, o, &checked, total](const FileRecord &r) {
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
return false;
|
||||||
const bool matched = cachedFileContains(r, o);
|
const bool matched = cachedFileContains(r, o);
|
||||||
const qsizetype done = checked.fetch_add(1, std::memory_order_relaxed) + 1;
|
const qsizetype done = checked.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||||
if (done == total || done % 32 == 0)
|
if (done == total || done % 32 == 0)
|
||||||
@@ -456,6 +465,8 @@ public slots:
|
|||||||
return matched;
|
return matched;
|
||||||
});
|
});
|
||||||
future.waitForFinished();
|
future.waitForFinished();
|
||||||
|
if (finishIfCancelled())
|
||||||
|
return;
|
||||||
candidates = future.results();
|
candidates = future.results();
|
||||||
if (o.maxResults > 0 && candidates.size() > o.maxResults)
|
if (o.maxResults > 0 && candidates.size() > o.maxResults)
|
||||||
candidates.resize(o.maxResults);
|
candidates.resize(o.maxResults);
|
||||||
@@ -480,6 +491,8 @@ public slots:
|
|||||||
int group = 1;
|
int group = 1;
|
||||||
QSet<QString> duplicatedPaths;
|
QSet<QString> duplicatedPaths;
|
||||||
for (auto &[size, sameSize] : bySize) {
|
for (auto &[size, sameSize] : bySize) {
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
Q_UNUSED(size);
|
Q_UNUSED(size);
|
||||||
if (sameSize.size() < 2)
|
if (sameSize.size() < 2)
|
||||||
continue;
|
continue;
|
||||||
@@ -490,18 +503,24 @@ public slots:
|
|||||||
emit phaseProgress(tr("Checking duplicates"), done, hashTotal);
|
emit phaseProgress(tr("Checking duplicates"), done, hashTotal);
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
QHash<QByteArray, QVector<FileRecord>> sameHash;
|
QHash<QByteArray, QVector<FileRecord>> sameHash;
|
||||||
for (auto &[hash, record] : hashes)
|
for (auto &[hash, record] : hashes)
|
||||||
if (!hash.isEmpty())
|
if (!hash.isEmpty())
|
||||||
sameHash[hash].push_back(record);
|
sameHash[hash].push_back(record);
|
||||||
for (const auto &hashCandidates : sameHash) {
|
for (const auto &hashCandidates : sameHash) {
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
if (hashCandidates.size() < 2)
|
if (hashCandidates.size() < 2)
|
||||||
continue;
|
continue;
|
||||||
QVector<QVector<FileRecord>> exactGroups;
|
QVector<QVector<FileRecord>> exactGroups;
|
||||||
for (const auto &record : hashCandidates) {
|
for (const auto &record : hashCandidates) {
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
bool placed = false;
|
bool placed = false;
|
||||||
for (auto &exact : exactGroups) {
|
for (auto &exact : exactGroups) {
|
||||||
if (filesEqual(exact.front().path, record.path)) {
|
if (filesEqual(exact.front().path, record.path, &cancelled_)) {
|
||||||
exact.push_back(record);
|
exact.push_back(record);
|
||||||
placed = true;
|
placed = true;
|
||||||
break;
|
break;
|
||||||
@@ -511,6 +530,8 @@ public slots:
|
|||||||
exactGroups.push_back({record});
|
exactGroups.push_back({record});
|
||||||
}
|
}
|
||||||
for (auto &exact : exactGroups) {
|
for (auto &exact : exactGroups) {
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
if (exact.size() < 2)
|
if (exact.size() < 2)
|
||||||
continue;
|
continue;
|
||||||
int duplicateCopy = 1;
|
int duplicateCopy = 1;
|
||||||
@@ -528,6 +549,8 @@ public slots:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (finishIfCancelled())
|
||||||
|
return;
|
||||||
if (o.mode == QStringLiteral("Non-Duplicates search")) {
|
if (o.mode == QStringLiteral("Non-Duplicates search")) {
|
||||||
for (const auto &record : candidates)
|
for (const auto &record : candidates)
|
||||||
if (record.type == QStringLiteral("File") && !duplicatedPaths.contains(record.path))
|
if (record.type == QStringLiteral("File") && !duplicatedPaths.contains(record.path))
|
||||||
@@ -545,6 +568,8 @@ public slots:
|
|||||||
quint64 namesDone = 0;
|
quint64 namesDone = 0;
|
||||||
emit phaseProgress(tr("Comparing duplicate names"), 0, byName.size());
|
emit phaseProgress(tr("Comparing duplicate names"), 0, byName.size());
|
||||||
for (auto sameName : byName) {
|
for (auto sameName : byName) {
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
++namesDone;
|
++namesDone;
|
||||||
if (namesDone % 32 == 0 || namesDone == quint64(byName.size()))
|
if (namesDone % 32 == 0 || namesDone == quint64(byName.size()))
|
||||||
emit phaseProgress(tr("Comparing duplicate names"), namesDone, byName.size());
|
emit phaseProgress(tr("Comparing duplicate names"), namesDone, byName.size());
|
||||||
@@ -558,7 +583,8 @@ public slots:
|
|||||||
bool identical = !firstHash.isEmpty();
|
bool identical = !firstHash.isEmpty();
|
||||||
for (qsizetype i = 1; identical && i < sameName.size(); ++i) {
|
for (qsizetype i = 1; identical && i < sameName.size(); ++i) {
|
||||||
identical = cachedSha256(sameName[i]) == firstHash
|
identical = cachedSha256(sameName[i]) == firstHash
|
||||||
&& filesEqual(sameName.front().path, sameName[i].path);
|
&& filesEqual(
|
||||||
|
sameName.front().path, sameName[i].path, &cancelled_);
|
||||||
}
|
}
|
||||||
const bool wantsNonIdentical = o.duplicateNameMode.startsWith(QStringLiteral("Only non"));
|
const bool wantsNonIdentical = o.duplicateNameMode.startsWith(QStringLiteral("Only non"));
|
||||||
if (identical == wantsNonIdentical)
|
if (identical == wantsNonIdentical)
|
||||||
@@ -572,9 +598,13 @@ public slots:
|
|||||||
}
|
}
|
||||||
++group;
|
++group;
|
||||||
}
|
}
|
||||||
|
if (finishIfCancelled())
|
||||||
|
return;
|
||||||
} else if (o.mode == QStringLiteral("Summary")) {
|
} else if (o.mode == QStringLiteral("Summary")) {
|
||||||
QHash<QString, QVector<FileRecord>> byFolder;
|
QHash<QString, QVector<FileRecord>> byFolder;
|
||||||
for (const auto &record : candidates) {
|
for (const auto &record : candidates) {
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
byFolder[record.folder].push_back(record);
|
byFolder[record.folder].push_back(record);
|
||||||
if (o.includeSubfoldersInSummary) {
|
if (o.includeSubfoldersInSummary) {
|
||||||
QDir parent(record.folder);
|
QDir parent(record.folder);
|
||||||
@@ -594,6 +624,8 @@ public slots:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto it = byFolder.cbegin(); it != byFolder.cend(); ++it) {
|
for (auto it = byFolder.cbegin(); it != byFolder.cend(); ++it) {
|
||||||
|
if (cancelled_.load(std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
qint64 total = 0;
|
qint64 total = 0;
|
||||||
qint64 totalOnDisk = 0;
|
qint64 totalOnDisk = 0;
|
||||||
qint64 newest = 0;
|
qint64 newest = 0;
|
||||||
@@ -607,10 +639,14 @@ public slots:
|
|||||||
newest, 0, 0, 0, tr("%1 files").arg(it.value().size()),
|
newest, 0, 0, 0, tr("%1 files").arg(it.value().size()),
|
||||||
{}, {}, 0, 0});
|
{}, {}, 0, 0});
|
||||||
}
|
}
|
||||||
|
if (finishIfCancelled())
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
results = std::move(candidates);
|
results = std::move(candidates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (finishIfCancelled())
|
||||||
|
return;
|
||||||
const double seconds = std::chrono::duration<double>(
|
const double seconds = std::chrono::duration<double>(
|
||||||
std::chrono::steady_clock::now() - started).count();
|
std::chrono::steady_clock::now() - started).count();
|
||||||
cache_.save();
|
cache_.save();
|
||||||
@@ -634,8 +670,9 @@ private:
|
|||||||
return digest;
|
return digest;
|
||||||
}
|
}
|
||||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||||
digest = sha256(record.path);
|
digest = sha256(record.path, &cancelled_);
|
||||||
cache_.storeHash(record, digest);
|
if (!cancelled_.load(std::memory_order_relaxed))
|
||||||
|
cache_.storeHash(record, digest);
|
||||||
return digest;
|
return digest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,8 +687,9 @@ private:
|
|||||||
return *cached;
|
return *cached;
|
||||||
}
|
}
|
||||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||||
const bool matched = fileContains(record.path, o);
|
const bool matched = fileContains(record.path, o, &cancelled_);
|
||||||
cache_.storeContent(record, queryKey, matched);
|
if (!cancelled_.load(std::memory_order_relaxed))
|
||||||
|
cache_.storeContent(record, queryKey, matched);
|
||||||
return matched;
|
return matched;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,12 +1353,16 @@ public:
|
|||||||
statusBar()->showMessage(tr("Search cache cleared"), 5000);
|
statusBar()->showMessage(tr("Search cache cleared"), 5000);
|
||||||
});
|
});
|
||||||
connect(engine_, &SearchEngine::progress, this, [this](const QString &path, quint64 count) {
|
connect(engine_, &SearchEngine::progress, this, [this](const QString &path, quint64 count) {
|
||||||
|
if (isStopping_)
|
||||||
|
return;
|
||||||
progress_->setRange(0, 0);
|
progress_->setRange(0, 0);
|
||||||
progress_->setFormat(tr("Scanning… %1 entries").arg(count));
|
progress_->setFormat(tr("Scanning… %1 entries").arg(count));
|
||||||
statusBar()->showMessage(tr("Scanning %1 (%2 entries)").arg(path).arg(count));
|
statusBar()->showMessage(tr("Scanning %1 (%2 entries)").arg(path).arg(count));
|
||||||
});
|
});
|
||||||
connect(engine_, &SearchEngine::phaseProgress, this,
|
connect(engine_, &SearchEngine::phaseProgress, this,
|
||||||
[this](const QString &phase, quint64 completed, quint64 total) {
|
[this](const QString &phase, quint64 completed, quint64 total) {
|
||||||
|
if (isStopping_)
|
||||||
|
return;
|
||||||
if (total == 0) {
|
if (total == 0) {
|
||||||
progress_->setRange(0, 0);
|
progress_->setRange(0, 0);
|
||||||
progress_->setFormat(completed
|
progress_->setFormat(completed
|
||||||
@@ -1342,7 +1384,14 @@ public:
|
|||||||
workerThread_.start();
|
workerThread_.start();
|
||||||
|
|
||||||
connect(searchAction_, &QAction::triggered, this, &MainWindow::showOptions);
|
connect(searchAction_, &QAction::triggered, this, &MainWindow::showOptions);
|
||||||
connect(stopAction_, &QAction::triggered, this, [this] { emit stopRequested(); });
|
connect(stopAction_, &QAction::triggered, this, [this] {
|
||||||
|
isStopping_ = true;
|
||||||
|
stopAction_->setEnabled(false);
|
||||||
|
progress_->setRange(0, 0);
|
||||||
|
progress_->setFormat(tr("Stopping…"));
|
||||||
|
statusBar()->showMessage(tr("Stopping search…"));
|
||||||
|
emit stopRequested();
|
||||||
|
});
|
||||||
connect(refreshAction_, &QAction::triggered, this, &MainWindow::refreshSearch);
|
connect(refreshAction_, &QAction::triggered, this, &MainWindow::refreshSearch);
|
||||||
connect(exportAction, &QAction::triggered, this, &MainWindow::exportResults);
|
connect(exportAction, &QAction::triggered, this, &MainWindow::exportResults);
|
||||||
connect(openAction, &QAction::triggered, this, &MainWindow::openSelected);
|
connect(openAction, &QAction::triggered, this, &MainWindow::openSelected);
|
||||||
@@ -1524,6 +1573,7 @@ private slots:
|
|||||||
if (isSearching_ || options_.roots.trimmed().isEmpty())
|
if (isSearching_ || options_.roots.trimmed().isEmpty())
|
||||||
return;
|
return;
|
||||||
isSearching_ = true;
|
isSearching_ = true;
|
||||||
|
isStopping_ = false;
|
||||||
lastSearchOptions_ = options_;
|
lastSearchOptions_ = options_;
|
||||||
updateStaleSearchWarning();
|
updateStaleSearchWarning();
|
||||||
applyDisplayOptions();
|
applyDisplayOptions();
|
||||||
@@ -1544,6 +1594,7 @@ private slots:
|
|||||||
void searchFinished(const QVector<FileRecord> &results, const QString &message)
|
void searchFinished(const QVector<FileRecord> &results, const QString &message)
|
||||||
{
|
{
|
||||||
isSearching_ = false;
|
isSearching_ = false;
|
||||||
|
isStopping_ = false;
|
||||||
model_->setRows(results);
|
model_->setRows(results);
|
||||||
searchAction_->setEnabled(true);
|
searchAction_->setEnabled(true);
|
||||||
stopAction_->setEnabled(false);
|
stopAction_->setEnabled(false);
|
||||||
@@ -2099,6 +2150,7 @@ private:
|
|||||||
bool showGrid_ = true;
|
bool showGrid_ = true;
|
||||||
bool showTooltips_ = true;
|
bool showTooltips_ = true;
|
||||||
bool isSearching_ = false;
|
bool isSearching_ = false;
|
||||||
|
bool isStopping_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
|||||||
+23
-8
@@ -94,8 +94,18 @@ std::optional<QByteArray> hexNeedle(QString text)
|
|||||||
return QByteArray::fromHex(text.toLatin1());
|
return QByteArray::fromHex(text.toLatin1());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileContains(const QString &path, const SearchOptions &options)
|
namespace {
|
||||||
|
bool isCancelled(const std::atomic_bool *cancelled)
|
||||||
{
|
{
|
||||||
|
return cancelled && cancelled->load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileContains(const QString &path, const SearchOptions &options,
|
||||||
|
const std::atomic_bool *cancelled)
|
||||||
|
{
|
||||||
|
if (isCancelled(cancelled))
|
||||||
|
return false;
|
||||||
if (options.contains.isEmpty())
|
if (options.contains.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -124,7 +134,7 @@ bool fileContains(const QString &path, const SearchOptions &options)
|
|||||||
for (const QByteArray &needle : needles)
|
for (const QByteArray &needle : needles)
|
||||||
overlap = std::max(overlap, needle.size());
|
overlap = std::max(overlap, needle.size());
|
||||||
QByteArray tail;
|
QByteArray tail;
|
||||||
while (!file.atEnd()) {
|
while (!file.atEnd() && !isCancelled(cancelled)) {
|
||||||
QByteArray data = tail + file.read(1024 * 1024);
|
QByteArray data = tail + file.read(1024 * 1024);
|
||||||
if (!options.caseSensitive && !options.binary)
|
if (!options.caseSensitive && !options.binary)
|
||||||
data = data.toLower();
|
data = data.toLower();
|
||||||
@@ -145,31 +155,36 @@ bool fileContains(const QString &path, const SearchOptions &options)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool filesEqual(const QString &leftPath, const QString &rightPath)
|
bool filesEqual(const QString &leftPath, const QString &rightPath,
|
||||||
|
const std::atomic_bool *cancelled)
|
||||||
{
|
{
|
||||||
|
if (isCancelled(cancelled))
|
||||||
|
return false;
|
||||||
QFile left(leftPath);
|
QFile left(leftPath);
|
||||||
QFile right(rightPath);
|
QFile right(rightPath);
|
||||||
if (!left.open(QIODevice::ReadOnly) || !right.open(QIODevice::ReadOnly)
|
if (!left.open(QIODevice::ReadOnly) || !right.open(QIODevice::ReadOnly)
|
||||||
|| left.size() != right.size())
|
|| left.size() != right.size())
|
||||||
return false;
|
return false;
|
||||||
while (!left.atEnd()) {
|
while (!left.atEnd() && !isCancelled(cancelled)) {
|
||||||
const QByteArray leftData = left.read(1024 * 1024);
|
const QByteArray leftData = left.read(1024 * 1024);
|
||||||
const QByteArray rightData = right.read(leftData.size());
|
const QByteArray rightData = right.read(leftData.size());
|
||||||
if (leftData != rightData)
|
if (leftData != rightData)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return right.atEnd();
|
return !isCancelled(cancelled) && right.atEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray sha256(const QString &path)
|
QByteArray sha256(const QString &path, const std::atomic_bool *cancelled)
|
||||||
{
|
{
|
||||||
|
if (isCancelled(cancelled))
|
||||||
|
return {};
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
return {};
|
return {};
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha256);
|
QCryptographicHash hash(QCryptographicHash::Sha256);
|
||||||
while (!file.atEnd())
|
while (!file.atEnd() && !isCancelled(cancelled))
|
||||||
hash.addData(file.read(1024 * 1024));
|
hash.addData(file.read(1024 * 1024));
|
||||||
return hash.result();
|
return isCancelled(cancelled) ? QByteArray{} : hash.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parseOptionalIsoDate(const QString &text, qint64 &seconds)
|
bool parseOptionalIsoDate(const QString &text, qint64 &seconds)
|
||||||
|
|||||||
+6
-3
@@ -7,6 +7,7 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
@@ -76,8 +77,10 @@ bool wildcardMatch(const QString &value, const QStringList &items, Qt::CaseSensi
|
|||||||
QString humanSize(qint64 bytes);
|
QString humanSize(qint64 bytes);
|
||||||
QString ownerName(uid_t uid);
|
QString ownerName(uid_t uid);
|
||||||
std::optional<QByteArray> hexNeedle(QString text);
|
std::optional<QByteArray> hexNeedle(QString text);
|
||||||
bool fileContains(const QString &path, const SearchOptions &options);
|
bool fileContains(const QString &path, const SearchOptions &options,
|
||||||
bool filesEqual(const QString &leftPath, const QString &rightPath);
|
const std::atomic_bool *cancelled = nullptr);
|
||||||
QByteArray sha256(const QString &path);
|
bool filesEqual(const QString &leftPath, const QString &rightPath,
|
||||||
|
const std::atomic_bool *cancelled = nullptr);
|
||||||
|
QByteArray sha256(const QString &path, const std::atomic_bool *cancelled = nullptr);
|
||||||
bool parseOptionalIsoDate(const QString &text, qint64 &seconds);
|
bool parseOptionalIsoDate(const QString &text, qint64 &seconds);
|
||||||
bool shouldShowDuplicateResult(bool copiesOnly, int keeperPriority);
|
bool shouldShowDuplicateResult(bool copiesOnly, int keeperPriority);
|
||||||
|
|||||||
@@ -90,6 +90,24 @@ private slots:
|
|||||||
QVERIFY(!filesEqual(firstPath, differentPath));
|
QVERIFY(!filesEqual(firstPath, differentPath));
|
||||||
QCOMPARE(sha256(firstPath), QCryptographicHash::hash(content, QCryptographicHash::Sha256));
|
QCOMPARE(sha256(firstPath), QCryptographicHash::hash(content, QCryptographicHash::Sha256));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cancelsFileOperations()
|
||||||
|
{
|
||||||
|
QTemporaryDir directory;
|
||||||
|
QVERIFY(directory.isValid());
|
||||||
|
const QString path = directory.filePath(QStringLiteral("file.bin"));
|
||||||
|
QFile file(path);
|
||||||
|
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||||
|
QCOMPARE(file.write("needle"), qint64(6));
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
std::atomic_bool cancelled = true;
|
||||||
|
SearchOptions options;
|
||||||
|
options.contains = QStringLiteral("needle");
|
||||||
|
QVERIFY(!fileContains(path, options, &cancelled));
|
||||||
|
QVERIFY(!filesEqual(path, path, &cancelled));
|
||||||
|
QVERIFY(sha256(path, &cancelled).isEmpty());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(SearchCoreTest)
|
QTEST_GUILESS_MAIN(SearchCoreTest)
|
||||||
|
|||||||
Reference in New Issue
Block a user