Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions autotests/dfm-search-tests/tst_recent_search_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ QList<RecentItem> createTestItems()
};
}

// 包含黑名单目录下文件的测试数据,用于验证黑名单路径过滤
QList<RecentItem> createBlacklistTestItems()
{
const qint64 now = QDateTime::currentDateTime().toSecsSinceEpoch();
const qint64 yesterday = QDateTime::currentDateTime().addDays(-1).toSecsSinceEpoch();

return {
// 黑名单目录 /home/uos/Downloads 下的文件(应被过滤)
{"/home/uos/Downloads/report.pdf", "file:///home/uos/Downloads/report.pdf", now},
{"/home/uos/Downloads/notes.txt", "file:///home/uos/Downloads/notes.txt", yesterday},
{"/home/uos/Downloads/sub/deep.xlsx", "file:///home/uos/Downloads/sub/deep.xlsx", now},
// 名称前缀相同但非黑名单子目录的文件(应保留——路径边界匹配)
{"/home/uos/Downloads_backup/archive.zip", "file:///home/uos/Downloads_backup/archive.zip", now},
// 非黑名单目录下的文件(应保留)
{"/home/uos/Documents/plan.docx", "file:///home/uos/Documents/plan.docx", yesterday},
{"/tmp/test/notes.txt", "file:///tmp/test/notes.txt", yesterday},
};
}

} // namespace

// 可测试的 RecentSearchStrategy 子类,提供访问 fetchAddr 的接口
Expand Down Expand Up @@ -76,6 +95,10 @@ private Q_SLOTS:
void search_detailedResults_containsExtendedAttributes();
void search_maxResults_truncatesResultCount();
void search_resultFoundEnabled_emitsSignalPerItem();
void search_excludedPathsFilter_removesBlacklistedItems();
void search_excludedPathsFilter_emptyKeepsAllItems();
void search_excludedPathsCombinedWithKeyword_appliesBoth();
void search_excludedPathsFilter_respectsPathBoundary();
};

void tst_RecentSearchEngine::initTestCase()
Expand Down Expand Up @@ -328,6 +351,121 @@ void tst_RecentSearchEngine::search_resultFoundEnabled_emitsSignalPerItem()
QCOMPARE(finishedSpy.count(), 1);
}

void tst_RecentSearchEngine::search_excludedPathsFilter_removesBlacklistedItems()
{
SearchOptions opts;
opts.setSearchExcludedPaths({"/home/uos/Downloads"});
TestableRecentStrategy strategy(opts);
const auto testItems = createBlacklistTestItems();

stub_ext::StubExt stub;
stub.set_lamda(strategy.fetchAddr(), [testItems]() -> QList<RecentItem> {
return testItems;
});

QSignalSpy finishedSpy(&strategy, &BaseSearchStrategy::searchFinished);
SearchQuery query = SearchFactory::createQuery("");
strategy.search(query);

QCOMPARE(finishedSpy.count(), 1);
SearchResultList results = finishedSpy.takeFirst().at(0).value<SearchResultList>();
QStringList paths = resultPaths(results);

// 黑名单目录 /home/uos/Downloads 下的 3 个文件被过滤;
// /home/uos/Downloads_backup/archive.zip 因路径边界匹配而保留,共 3 个
QCOMPARE(paths.size(), 3);
QVERIFY(paths.contains("/home/uos/Documents/plan.docx"));
QVERIFY(paths.contains("/tmp/test/notes.txt"));
QVERIFY(paths.contains("/home/uos/Downloads_backup/archive.zip"));
QVERIFY(!paths.contains("/home/uos/Downloads/report.pdf"));
QVERIFY(!paths.contains("/home/uos/Downloads/notes.txt"));
QVERIFY(!paths.contains("/home/uos/Downloads/sub/deep.xlsx"));
}

void tst_RecentSearchEngine::search_excludedPathsFilter_emptyKeepsAllItems()
{
SearchOptions opts;
// 不设置黑名单路径
TestableRecentStrategy strategy(opts);
const auto testItems = createBlacklistTestItems();

stub_ext::StubExt stub;
stub.set_lamda(strategy.fetchAddr(), [testItems]() -> QList<RecentItem> {
return testItems;
});

QSignalSpy finishedSpy(&strategy, &BaseSearchStrategy::searchFinished);
SearchQuery query = SearchFactory::createQuery("");
strategy.search(query);

QCOMPARE(finishedSpy.count(), 1);
SearchResultList results = finishedSpy.takeFirst().at(0).value<SearchResultList>();
// 无黑名单时全部保留
QCOMPARE(results.size(), testItems.size());
}

void tst_RecentSearchEngine::search_excludedPathsCombinedWithKeyword_appliesBoth()
{
SearchOptions opts;
opts.setSearchExcludedPaths({"/home/uos/Downloads"});
TestableRecentStrategy strategy(opts);
const auto testItems = createBlacklistTestItems();

stub_ext::StubExt stub;
stub.set_lamda(strategy.fetchAddr(), [testItems]() -> QList<RecentItem> {
return testItems;
});

QSignalSpy finishedSpy(&strategy, &BaseSearchStrategy::searchFinished);
// 关键词 "notes" 命中 /home/uos/Downloads/notes.txt(被黑名单过滤)与 /tmp/test/notes.txt(保留)
SearchQuery query = SearchFactory::createQuery("notes");
strategy.search(query);

QCOMPARE(finishedSpy.count(), 1);
SearchResultList results = finishedSpy.takeFirst().at(0).value<SearchResultList>();
QStringList paths = resultPaths(results);

QCOMPARE(paths.size(), 1);
QVERIFY(paths.contains("/tmp/test/notes.txt"));
QVERIFY(!paths.contains("/home/uos/Downloads/notes.txt"));
}

void tst_RecentSearchEngine::search_excludedPathsFilter_respectsPathBoundary()
{
// 验证路径边界匹配:黑名单 /home/uos/Downloads 不应误匹配 /home/uos/Downloads_backup
SearchOptions opts;
opts.setSearchExcludedPaths({"/home/uos/Downloads"});
TestableRecentStrategy strategy(opts);

const qint64 now = QDateTime::currentDateTime().toSecsSinceEpoch();
const QList<RecentItem> boundaryItems = {
// 黑名单目录下的文件(应过滤)
{"/home/uos/Downloads/file.txt", "file:///home/uos/Downloads/file.txt", now},
// 名称前缀相同但非子目录(应保留)
{"/home/uos/Downloads_backup/file.txt", "file:///home/uos/Downloads_backup/file.txt", now},
{"/home/uos/Downloads_archive/file.txt", "file:///home/uos/Downloads_archive/file.txt", now},
};

stub_ext::StubExt stub;
stub.set_lamda(strategy.fetchAddr(), [boundaryItems]() -> QList<RecentItem> {
return boundaryItems;
});

QSignalSpy finishedSpy(&strategy, &BaseSearchStrategy::searchFinished);
SearchQuery query = SearchFactory::createQuery("");
strategy.search(query);

QCOMPARE(finishedSpy.count(), 1);
SearchResultList results = finishedSpy.takeFirst().at(0).value<SearchResultList>();
QStringList paths = resultPaths(results);

// 仅 /home/uos/Downloads/file.txt 被过滤,_backup 和 _archive 因路径边界而保留
QCOMPARE(paths.size(), 2);
QVERIFY(paths.contains("/home/uos/Downloads_backup/file.txt"));
QVERIFY(paths.contains("/home/uos/Downloads_archive/file.txt"));
QVERIFY(!paths.contains("/home/uos/Downloads/file.txt"));
}

QObject *create_tst_RecentSearchEngine()
{
return new tst_RecentSearchEngine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
#include <QDBusReply>
#include <QElapsedTimer>
#include <QJsonArray>
#include <QJsonDocument>

Check warning on line 12 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QJsonDocument> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 12 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QJsonDocument> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QJsonObject>

Check warning on line 13 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QJsonObject> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 13 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QJsonObject> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QFileInfo>

Check warning on line 14 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QFileInfo> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 14 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QFileInfo> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDir>

Check warning on line 15 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 15 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QDir> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDateTime>

Check warning on line 16 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 16 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QDateTime> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDebug>

Check warning on line 17 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDebug> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 17 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <QDebug> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <algorithm>

Check warning on line 18 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <algorithm> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 18 in src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp

View workflow job for this annotation

GitHub Actions / static-check / static-check

Include file: <algorithm> not found. Please note: Cppcheck does not need standard library headers to get proper results.

DFM_SEARCH_BEGIN_NS

Expand Down Expand Up @@ -136,6 +138,32 @@
return out;
}

QList<RecentItem> RecentSearchStrategy::filterByExcludedPaths(const QList<RecentItem> &items) const
{
QStringList excludedPaths = m_options.searchExcludedPaths();
if (excludedPaths.isEmpty())
return items;

// 标准化路径:去除末尾斜杠、解析 "."/"..",确保路径边界匹配可靠。
for (auto &p : excludedPaths)
p = QDir::cleanPath(p);

QList<RecentItem> out;
for (const RecentItem &item : std::as_const(items)) {
const bool excluded = std::any_of(excludedPaths.cbegin(), excludedPaths.cend(),
[&item](const QString &excludedPath) {
// 路径边界匹配:仅排除 excludedPath 自身或其子路径,
// 避免 /home/uos/Downloads 误匹配 /home/uos/Downloads_backup。
return item.path == excludedPath
|| item.path.startsWith(excludedPath + QLatin1Char('/'));
});
if (!excluded) {
out.append(item);
}
}
return out;
}

// ── 结果构建 ───────────────────────────────────────────────────────

SearchResult RecentSearchStrategy::toSearchResult(const RecentItem &item) const
Expand Down Expand Up @@ -180,7 +208,10 @@
return;
}

// Step 2: 关键词过滤(仅 Simple 类型有 keyword;Boolean/Wildcard 暂不支持)
// Step 2: 黑名单路径过滤(尽早排除黑名单目录下的条目,减少后续过滤工作量)
items = filterByExcludedPaths(items);

// Step 3: 关键词过滤(仅 Simple 类型有 keyword;Boolean/Wildcard 暂不支持)
QString keyword;
if (query.type() == SearchQuery::Type::Simple) {
keyword = query.keyword();
Expand All @@ -190,17 +221,17 @@
}
items = filterByKeyword(items, keyword);

// Step 3: 扩展名过滤
// Step 4: 扩展名过滤
FileNameOptionsAPI optApi(const_cast<SearchOptions &>(m_options));
const QStringList exts = optApi.fileExtensions();
items = filterByExtensions(items, exts);

// Step 4: 时间范围过滤(基于 modified 时间戳)
// Step 5: 时间范围过滤(基于 modified 时间戳)
if (m_options.hasTimeRangeFilter()) {
items = filterByTimeRange(items);
}

// Step 5: 构建 SearchResult 并发射
// Step 6: 构建 SearchResult 并发射
const bool resultFoundEnabled = m_options.resultFoundEnabled();
const int maxResults = m_options.maxResults() > 0 ? m_options.maxResults() : INT_MAX;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ class RecentSearchStrategy : public BaseSearchStrategy
*/
QList<RecentItem> filterByTimeRange(const QList<RecentItem> &items) const;

/**
* @brief 按黑名单路径过滤。
*
* 排除 path 等于或位于黑名单目录下的最近使用项,
* 采用路径边界匹配(excludedPath 或 excludedPath + "/"),
* 避免 /home/uos/Downloads 误匹配 /home/uos/Downloads_backup。
* 与 FileNameRealTimeStrategy 的排除语义保持一致。
* 空列表时不过滤。
*/
QList<RecentItem> filterByExcludedPaths(const QList<RecentItem> &items) const;

/**
* @brief 将 RecentItem 转换为 SearchResult,填充详细属性。
*/
Expand Down
Loading