Compare commits
3 Commits
v0.2.4
...
afb089bee6
| Author | SHA1 | Date | |
|---|---|---|---|
| afb089bee6 | |||
| 79001370b8 | |||
| 07fc419f1e |
+97
-21
@@ -255,6 +255,12 @@ public slots:
|
||||
void search(const SearchOptions &o)
|
||||
{
|
||||
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;
|
||||
cache_.setEnabled(o.useCache);
|
||||
cacheHits_.store(0, std::memory_order_relaxed);
|
||||
@@ -284,7 +290,8 @@ public slots:
|
||||
dirOptions |= fs::directory_options::follow_directory_symlink;
|
||||
if (!o.recursive) {
|
||||
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;
|
||||
if (totalEntries % 2048 == 0)
|
||||
emit phaseProgress(tr("Counting files"), totalEntries, 0);
|
||||
@@ -397,7 +404,9 @@ public slots:
|
||||
|
||||
if (!o.recursive) {
|
||||
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);
|
||||
if (++scanned % 512 == 0) {
|
||||
if (totalEntries)
|
||||
@@ -438,10 +447,8 @@ public slots:
|
||||
if (totalEntries)
|
||||
emit phaseProgress(tr("Scanning files"), std::min(scanned, totalEntries), totalEntries);
|
||||
|
||||
if (cancelled_.load(std::memory_order_relaxed)) {
|
||||
emit finished({}, tr("Search stopped"));
|
||||
if (finishIfCancelled())
|
||||
return;
|
||||
}
|
||||
|
||||
if (!o.contains.isEmpty() && !o.findFolders) {
|
||||
emit phaseProgress(tr("Searching file contents"), 0, candidates.size());
|
||||
@@ -449,6 +456,8 @@ public slots:
|
||||
const qsizetype total = candidates.size();
|
||||
QFuture<FileRecord> future = QtConcurrent::filtered(
|
||||
candidates, [this, o, &checked, total](const FileRecord &r) {
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
return false;
|
||||
const bool matched = cachedFileContains(r, o);
|
||||
const qsizetype done = checked.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
if (done == total || done % 32 == 0)
|
||||
@@ -456,6 +465,8 @@ public slots:
|
||||
return matched;
|
||||
});
|
||||
future.waitForFinished();
|
||||
if (finishIfCancelled())
|
||||
return;
|
||||
candidates = future.results();
|
||||
if (o.maxResults > 0 && candidates.size() > o.maxResults)
|
||||
candidates.resize(o.maxResults);
|
||||
@@ -480,6 +491,8 @@ public slots:
|
||||
int group = 1;
|
||||
QSet<QString> duplicatedPaths;
|
||||
for (auto &[size, sameSize] : bySize) {
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
break;
|
||||
Q_UNUSED(size);
|
||||
if (sameSize.size() < 2)
|
||||
continue;
|
||||
@@ -490,18 +503,24 @@ public slots:
|
||||
emit phaseProgress(tr("Checking duplicates"), done, hashTotal);
|
||||
return result;
|
||||
});
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
break;
|
||||
QHash<QByteArray, QVector<FileRecord>> sameHash;
|
||||
for (auto &[hash, record] : hashes)
|
||||
if (!hash.isEmpty())
|
||||
sameHash[hash].push_back(record);
|
||||
for (const auto &hashCandidates : sameHash) {
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
break;
|
||||
if (hashCandidates.size() < 2)
|
||||
continue;
|
||||
QVector<QVector<FileRecord>> exactGroups;
|
||||
for (const auto &record : hashCandidates) {
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
break;
|
||||
bool placed = false;
|
||||
for (auto &exact : exactGroups) {
|
||||
if (filesEqual(exact.front().path, record.path)) {
|
||||
if (filesEqual(exact.front().path, record.path, &cancelled_)) {
|
||||
exact.push_back(record);
|
||||
placed = true;
|
||||
break;
|
||||
@@ -511,6 +530,8 @@ public slots:
|
||||
exactGroups.push_back({record});
|
||||
}
|
||||
for (auto &exact : exactGroups) {
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
break;
|
||||
if (exact.size() < 2)
|
||||
continue;
|
||||
int duplicateCopy = 1;
|
||||
@@ -518,21 +539,22 @@ public slots:
|
||||
record.group = group;
|
||||
record.duplicateCopy = duplicateCopy++;
|
||||
duplicatedPaths.insert(record.path);
|
||||
if (o.mode == QStringLiteral("Duplicates search"))
|
||||
if (o.mode == QStringLiteral("Duplicates search")
|
||||
&& shouldShowDuplicateResult(
|
||||
o.showDuplicateCopiesOnly, record.duplicateCopy)) {
|
||||
results.push_back(record);
|
||||
}
|
||||
}
|
||||
++group;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (finishIfCancelled())
|
||||
return;
|
||||
if (o.mode == QStringLiteral("Non-Duplicates search")) {
|
||||
for (const auto &record : candidates)
|
||||
if (record.type == QStringLiteral("File") && !duplicatedPaths.contains(record.path))
|
||||
results.push_back(record);
|
||||
} else if (!o.showOnlyDuplicateFiles) {
|
||||
for (const auto &record : candidates)
|
||||
if (record.type == QStringLiteral("File") && !duplicatedPaths.contains(record.path))
|
||||
results.push_back(record);
|
||||
}
|
||||
} else if (o.mode == QStringLiteral("Duplicate names search")) {
|
||||
QHash<QString, QVector<FileRecord>> byName;
|
||||
@@ -546,6 +568,8 @@ public slots:
|
||||
quint64 namesDone = 0;
|
||||
emit phaseProgress(tr("Comparing duplicate names"), 0, byName.size());
|
||||
for (auto sameName : byName) {
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
break;
|
||||
++namesDone;
|
||||
if (namesDone % 32 == 0 || namesDone == quint64(byName.size()))
|
||||
emit phaseProgress(tr("Comparing duplicate names"), namesDone, byName.size());
|
||||
@@ -559,7 +583,8 @@ public slots:
|
||||
bool identical = !firstHash.isEmpty();
|
||||
for (qsizetype i = 1; identical && i < sameName.size(); ++i) {
|
||||
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"));
|
||||
if (identical == wantsNonIdentical)
|
||||
@@ -573,9 +598,13 @@ public slots:
|
||||
}
|
||||
++group;
|
||||
}
|
||||
if (finishIfCancelled())
|
||||
return;
|
||||
} else if (o.mode == QStringLiteral("Summary")) {
|
||||
QHash<QString, QVector<FileRecord>> byFolder;
|
||||
for (const auto &record : candidates) {
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
break;
|
||||
byFolder[record.folder].push_back(record);
|
||||
if (o.includeSubfoldersInSummary) {
|
||||
QDir parent(record.folder);
|
||||
@@ -595,6 +624,8 @@ public slots:
|
||||
}
|
||||
}
|
||||
for (auto it = byFolder.cbegin(); it != byFolder.cend(); ++it) {
|
||||
if (cancelled_.load(std::memory_order_relaxed))
|
||||
break;
|
||||
qint64 total = 0;
|
||||
qint64 totalOnDisk = 0;
|
||||
qint64 newest = 0;
|
||||
@@ -608,10 +639,14 @@ public slots:
|
||||
newest, 0, 0, 0, tr("%1 files").arg(it.value().size()),
|
||||
{}, {}, 0, 0});
|
||||
}
|
||||
if (finishIfCancelled())
|
||||
return;
|
||||
} else {
|
||||
results = std::move(candidates);
|
||||
}
|
||||
|
||||
if (finishIfCancelled())
|
||||
return;
|
||||
const double seconds = std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now() - started).count();
|
||||
cache_.save();
|
||||
@@ -635,7 +670,8 @@ private:
|
||||
return digest;
|
||||
}
|
||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||
digest = sha256(record.path);
|
||||
digest = sha256(record.path, &cancelled_);
|
||||
if (!cancelled_.load(std::memory_order_relaxed))
|
||||
cache_.storeHash(record, digest);
|
||||
return digest;
|
||||
}
|
||||
@@ -651,7 +687,8 @@ private:
|
||||
return *cached;
|
||||
}
|
||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||
const bool matched = fileContains(record.path, o);
|
||||
const bool matched = fileContains(record.path, o, &cancelled_);
|
||||
if (!cancelled_.load(std::memory_order_relaxed))
|
||||
cache_.storeContent(record, queryKey, matched);
|
||||
return matched;
|
||||
}
|
||||
@@ -1221,14 +1258,14 @@ public:
|
||||
action->setCheckable(true);
|
||||
duplicateOptionsGroup->addAction(action);
|
||||
}
|
||||
showOnlyDuplicates->setChecked(options_.showOnlyDuplicateFiles);
|
||||
showAllDuplicates->setChecked(!options_.showOnlyDuplicateFiles);
|
||||
showOnlyDuplicates->setChecked(options_.showDuplicateCopiesOnly);
|
||||
showAllDuplicates->setChecked(!options_.showDuplicateCopiesOnly);
|
||||
connect(showOnlyDuplicates, &QAction::triggered, this, [this] {
|
||||
options_.showOnlyDuplicateFiles = true;
|
||||
options_.showDuplicateCopiesOnly = true;
|
||||
saveOptions();
|
||||
});
|
||||
connect(showAllDuplicates, &QAction::triggered, this, [this] {
|
||||
options_.showOnlyDuplicateFiles = false;
|
||||
options_.showDuplicateCopiesOnly = false;
|
||||
saveOptions();
|
||||
});
|
||||
|
||||
@@ -1288,6 +1325,15 @@ public:
|
||||
.arg(QStringLiteral(FOLDERSCOPE_VERSION)));
|
||||
});
|
||||
|
||||
staleSearchWarning_ = new QLabel(
|
||||
tr("⚠ Search settings changed — press F5 to rescan"));
|
||||
staleSearchWarning_->setStyleSheet(
|
||||
QStringLiteral("QLabel { color: #f0b429; font-weight: 600; padding: 0 6px; }"));
|
||||
staleSearchWarning_->setToolTip(
|
||||
tr("Displayed results were produced with different search settings."));
|
||||
staleSearchWarning_->hide();
|
||||
statusBar()->addPermanentWidget(staleSearchWarning_);
|
||||
|
||||
progress_ = new QProgressBar;
|
||||
progress_->setRange(0, 0);
|
||||
progress_->setMinimumWidth(280);
|
||||
@@ -1307,12 +1353,16 @@ public:
|
||||
statusBar()->showMessage(tr("Search cache cleared"), 5000);
|
||||
});
|
||||
connect(engine_, &SearchEngine::progress, this, [this](const QString &path, quint64 count) {
|
||||
if (isStopping_)
|
||||
return;
|
||||
progress_->setRange(0, 0);
|
||||
progress_->setFormat(tr("Scanning… %1 entries").arg(count));
|
||||
statusBar()->showMessage(tr("Scanning %1 (%2 entries)").arg(path).arg(count));
|
||||
});
|
||||
connect(engine_, &SearchEngine::phaseProgress, this,
|
||||
[this](const QString &phase, quint64 completed, quint64 total) {
|
||||
if (isStopping_)
|
||||
return;
|
||||
if (total == 0) {
|
||||
progress_->setRange(0, 0);
|
||||
progress_->setFormat(completed
|
||||
@@ -1334,7 +1384,14 @@ public:
|
||||
workerThread_.start();
|
||||
|
||||
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(exportAction, &QAction::triggered, this, &MainWindow::exportResults);
|
||||
connect(openAction, &QAction::triggered, this, &MainWindow::openSelected);
|
||||
@@ -1516,6 +1573,9 @@ private slots:
|
||||
if (isSearching_ || options_.roots.trimmed().isEmpty())
|
||||
return;
|
||||
isSearching_ = true;
|
||||
isStopping_ = false;
|
||||
lastSearchOptions_ = options_;
|
||||
updateStaleSearchWarning();
|
||||
applyDisplayOptions();
|
||||
model_->setRows({});
|
||||
searchAction_->setEnabled(false);
|
||||
@@ -1534,12 +1594,14 @@ private slots:
|
||||
void searchFinished(const QVector<FileRecord> &results, const QString &message)
|
||||
{
|
||||
isSearching_ = false;
|
||||
isStopping_ = false;
|
||||
model_->setRows(results);
|
||||
searchAction_->setEnabled(true);
|
||||
stopAction_->setEnabled(false);
|
||||
refreshAction_->setEnabled(true);
|
||||
progress_->hide();
|
||||
statusBar()->showMessage(message);
|
||||
updateStaleSearchWarning();
|
||||
if (autoSizeOnSearchEnd_) {
|
||||
table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
|
||||
table_->resizeColumnsToContents();
|
||||
@@ -1991,11 +2053,20 @@ private:
|
||||
settings.setValue(QStringLiteral("mode"), options_.mode);
|
||||
settings.setValue(QStringLiteral("duplicateNameMode"), options_.duplicateNameMode);
|
||||
settings.setValue(QStringLiteral("duplicateNameWithoutExtension"), options_.duplicateNameWithoutExtension);
|
||||
settings.setValue(QStringLiteral("showOnlyDuplicateFiles"), options_.showOnlyDuplicateFiles);
|
||||
settings.setValue(QStringLiteral("showDuplicateCopiesOnly"), options_.showDuplicateCopiesOnly);
|
||||
settings.setValue(QStringLiteral("includeSubfoldersInSummary"), options_.includeSubfoldersInSummary);
|
||||
settings.setValue(QStringLiteral("hidden"), options_.hidden);
|
||||
settings.setValue(QStringLiteral("readonly"), options_.readonly);
|
||||
settings.setValue(QStringLiteral("executable"), options_.executable);
|
||||
updateStaleSearchWarning();
|
||||
}
|
||||
|
||||
void updateStaleSearchWarning()
|
||||
{
|
||||
if (!staleSearchWarning_)
|
||||
return;
|
||||
staleSearchWarning_->setVisible(
|
||||
lastSearchOptions_.has_value() && options_ != *lastSearchOptions_);
|
||||
}
|
||||
|
||||
void loadOptions()
|
||||
@@ -2028,7 +2099,9 @@ private:
|
||||
options_.mode = s.value(QStringLiteral("mode"), options_.mode).toString();
|
||||
options_.duplicateNameMode = s.value(QStringLiteral("duplicateNameMode"), options_.duplicateNameMode).toString();
|
||||
options_.duplicateNameWithoutExtension = s.value(QStringLiteral("duplicateNameWithoutExtension"), false).toBool();
|
||||
options_.showOnlyDuplicateFiles = s.value(QStringLiteral("showOnlyDuplicateFiles"), true).toBool();
|
||||
options_.showDuplicateCopiesOnly =
|
||||
s.value(QStringLiteral("showDuplicateCopiesOnly"),
|
||||
s.value(QStringLiteral("showOnlyDuplicateFiles"), true)).toBool();
|
||||
options_.includeSubfoldersInSummary = s.value(QStringLiteral("includeSubfoldersInSummary"), false).toBool();
|
||||
options_.hidden = s.value(QStringLiteral("hidden"), options_.hidden).toString();
|
||||
options_.readonly = s.value(QStringLiteral("readonly"), options_.readonly).toString();
|
||||
@@ -2057,9 +2130,11 @@ private:
|
||||
QAction *searchAction_{};
|
||||
QAction *stopAction_{};
|
||||
QAction *refreshAction_{};
|
||||
QLabel *staleSearchWarning_{};
|
||||
QProgressBar *progress_{};
|
||||
QThread workerThread_;
|
||||
SearchEngine *engine_{};
|
||||
std::optional<SearchOptions> lastSearchOptions_;
|
||||
QString findQuery_;
|
||||
QString summarySizeUnitName_ = QStringLiteral("Automatic");
|
||||
QString doubleClickAction_ = QStringLiteral("Open Properties Window");
|
||||
@@ -2075,6 +2150,7 @@ private:
|
||||
bool showGrid_ = true;
|
||||
bool showTooltips_ = true;
|
||||
bool isSearching_ = false;
|
||||
bool isStopping_ = false;
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
|
||||
+30
-8
@@ -54,6 +54,13 @@ bool wildcardMatch(const QString &value, const QStringList &items, Qt::CaseSensi
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shouldShowDuplicateResult(bool copiesOnly, int keeperPriority)
|
||||
{
|
||||
if (keeperPriority < 1)
|
||||
return false;
|
||||
return !copiesOnly || keeperPriority > 1;
|
||||
}
|
||||
|
||||
QString humanSize(qint64 bytes)
|
||||
{
|
||||
static const QStringList units{QStringLiteral("B"), QStringLiteral("KB"),
|
||||
@@ -87,8 +94,18 @@ std::optional<QByteArray> hexNeedle(QString text)
|
||||
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())
|
||||
return true;
|
||||
|
||||
@@ -117,7 +134,7 @@ bool fileContains(const QString &path, const SearchOptions &options)
|
||||
for (const QByteArray &needle : needles)
|
||||
overlap = std::max(overlap, needle.size());
|
||||
QByteArray tail;
|
||||
while (!file.atEnd()) {
|
||||
while (!file.atEnd() && !isCancelled(cancelled)) {
|
||||
QByteArray data = tail + file.read(1024 * 1024);
|
||||
if (!options.caseSensitive && !options.binary)
|
||||
data = data.toLower();
|
||||
@@ -138,31 +155,36 @@ bool fileContains(const QString &path, const SearchOptions &options)
|
||||
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 right(rightPath);
|
||||
if (!left.open(QIODevice::ReadOnly) || !right.open(QIODevice::ReadOnly)
|
||||
|| left.size() != right.size())
|
||||
return false;
|
||||
while (!left.atEnd()) {
|
||||
while (!left.atEnd() && !isCancelled(cancelled)) {
|
||||
const QByteArray leftData = left.read(1024 * 1024);
|
||||
const QByteArray rightData = right.read(leftData.size());
|
||||
if (leftData != rightData)
|
||||
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);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return {};
|
||||
QCryptographicHash hash(QCryptographicHash::Sha256);
|
||||
while (!file.atEnd())
|
||||
while (!file.atEnd() && !isCancelled(cancelled))
|
||||
hash.addData(file.read(1024 * 1024));
|
||||
return hash.result();
|
||||
return isCancelled(cancelled) ? QByteArray{} : hash.result();
|
||||
}
|
||||
|
||||
bool parseOptionalIsoDate(const QString &text, qint64 &seconds)
|
||||
|
||||
+10
-4
@@ -7,6 +7,7 @@
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <sys/types.h>
|
||||
|
||||
@@ -40,11 +41,13 @@ struct SearchOptions {
|
||||
QString mode = QStringLiteral("Standard search");
|
||||
QString duplicateNameMode = QStringLiteral("All files and folders");
|
||||
bool duplicateNameWithoutExtension = false;
|
||||
bool showOnlyDuplicateFiles = true;
|
||||
bool showDuplicateCopiesOnly = true;
|
||||
bool includeSubfoldersInSummary = false;
|
||||
QString hidden = QStringLiteral("Any");
|
||||
QString readonly = QStringLiteral("Any");
|
||||
QString executable = QStringLiteral("Any");
|
||||
|
||||
bool operator==(const SearchOptions &) const = default;
|
||||
};
|
||||
|
||||
struct FileRecord {
|
||||
@@ -74,7 +77,10 @@ bool wildcardMatch(const QString &value, const QStringList &items, Qt::CaseSensi
|
||||
QString humanSize(qint64 bytes);
|
||||
QString ownerName(uid_t uid);
|
||||
std::optional<QByteArray> hexNeedle(QString text);
|
||||
bool fileContains(const QString &path, const SearchOptions &options);
|
||||
bool filesEqual(const QString &leftPath, const QString &rightPath);
|
||||
QByteArray sha256(const QString &path);
|
||||
bool fileContains(const QString &path, const SearchOptions &options,
|
||||
const std::atomic_bool *cancelled = nullptr);
|
||||
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 shouldShowDuplicateResult(bool copiesOnly, int keeperPriority);
|
||||
|
||||
@@ -33,6 +33,17 @@ private slots:
|
||||
QVERIFY(!parseOptionalIsoDate(QStringLiteral("2026-not-a-date"), seconds));
|
||||
}
|
||||
|
||||
void selectsDuplicateRowsForDisplay()
|
||||
{
|
||||
QVERIFY(!shouldShowDuplicateResult(true, 0));
|
||||
QVERIFY(!shouldShowDuplicateResult(true, 1));
|
||||
QVERIFY(shouldShowDuplicateResult(true, 2));
|
||||
|
||||
QVERIFY(!shouldShowDuplicateResult(false, 0));
|
||||
QVERIFY(shouldShowDuplicateResult(false, 1));
|
||||
QVERIFY(shouldShowDuplicateResult(false, 2));
|
||||
}
|
||||
|
||||
void searchesContentsAndHashes()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
@@ -79,6 +90,24 @@ private slots:
|
||||
QVERIFY(!filesEqual(firstPath, differentPath));
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user