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
66 changes: 66 additions & 0 deletions autotests/dfm-search-tests/tst_search_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ private Q_SLOTS:
void jsonOutput_contentAndFilenameCharCountContract();
void jsonOutput_ocrAndSemanticCharCountContract();
void textOutput_contentAndSemanticCharCountContract();
void jsonOutput_filenamePinyinContract();
void textOutput_filenamePinyinContract();
};

void tst_SearchOutput::previewOutputHelpers_includeCharCount()
Expand Down Expand Up @@ -298,6 +300,70 @@ void tst_SearchOutput::textOutput_contentAndSemanticCharCountContract()
QVERIFY(semanticText.contains("charCount: 30"));
}

void tst_SearchOutput::jsonOutput_filenamePinyinContract()
{
SearchOptions options;
options.setDetailedResultsEnabled(true);

SearchResult result("/tmp/baogao.txt");
FileNameResultAPI api(result);
api.setFilename(QString::fromUtf8("报告.txt"));
api.setPinyin("baogao");
api.setPinyinAcronym("bg");

dfmsearch::JsonOutput output(false);
output.setSearchOptions(options);
output.setSearchContext("bg", "/tmp", SearchType::FileName, SearchMethod::Indexed);
const QString jsonText = captureStdout([&]() {
output.outputSearchStarted();
output.outputSearchFinished({ result });
});
const QJsonObject root = QJsonDocument::fromJson(jsonText.toUtf8()).object();
const QJsonObject item = root.value("results").toArray().first().toObject();
QCOMPARE(item.value("pinyin").toString(), QString("baogao"));
QCOMPARE(item.value("pinyinAcronym").toString(), QString("bg"));

// 无拼音字段的结果不应输出空键
SearchResult plainResult("/tmp/readme.txt");
FileNameResultAPI plainApi(plainResult);
plainApi.setFilename("readme.txt");

dfmsearch::JsonOutput plainOutput(false);
plainOutput.setSearchOptions(options);
plainOutput.setSearchContext("readme", "/tmp", SearchType::FileName, SearchMethod::Indexed);
const QString plainText = captureStdout([&]() {
plainOutput.outputSearchStarted();
plainOutput.outputSearchFinished({ plainResult });
});
const QJsonObject plainRoot = QJsonDocument::fromJson(plainText.toUtf8()).object();
const QJsonObject plainItem = plainRoot.value("results").toArray().first().toObject();
QVERIFY(!plainItem.contains("pinyin"));
QVERIFY(!plainItem.contains("pinyinAcronym"));
}

void tst_SearchOutput::textOutput_filenamePinyinContract()
{
SearchOptions options;
options.setDetailedResultsEnabled(true);

SearchResult result("/tmp/baogao.txt");
FileNameResultAPI api(result);
api.setFilename(QString::fromUtf8("报告.txt"));
api.setFileType("doc");
api.setPinyin("baogao");
api.setPinyinAcronym("bg");

dfmsearch::TextOutput output;
output.setSearchOptions(options);
output.setVerbose(true);
output.setSearchContext("bg", "/tmp", SearchType::FileName, SearchMethod::Indexed);
const QString text = captureStdout([&]() {
output.outputSearchFinished({ result });
});
QVERIFY(text.contains("Pinyin: baogao"));
QVERIFY(text.contains("Pinyin acronym: bg"));
}

QObject *create_tst_SearchOutput()
{
return new tst_SearchOutput();
Expand Down
98 changes: 92 additions & 6 deletions autotests/dfm-search-tests/tst_search_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
#include <lucene++/PhraseQuery.h>
#include <lucene++/TermQuery.h>

#include <dfm-search/dsearch_global.h>

Check warning on line 16 in autotests/dfm-search-tests/tst_search_utils.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search/dsearch_global.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <dfm-search-lib/utils/filenameblacklistmatcher.h>

Check warning on line 17 in autotests/dfm-search-tests/tst_search_utils.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search-lib/utils/filenameblacklistmatcher.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <dfm-search-lib/utils/lucenequeryutils.h>

Check warning on line 18 in autotests/dfm-search-tests/tst_search_utils.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search-lib/utils/lucenequeryutils.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <dfm-search-lib/utils/searchutility.h>

Check warning on line 19 in autotests/dfm-search-tests/tst_search_utils.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <dfm-search-lib/utils/searchutility.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace DFMSEARCH;

Expand All @@ -30,6 +31,7 @@
void testPinyin();
void testPinyinAcronym();
void testAnythingStatus();
void testFileNameIndexStatusMapping();
void testFileNameBlacklistMatcher();
void testNGramSearchQuery();

Expand Down Expand Up @@ -238,15 +240,16 @@
// 测试状态获取
auto status = Global::fileNameIndexStatus();

// 结果验证
QVERIFY2(status.has_value(), "Could not retrieve status (file missing/permission error?)");
if (!status.has_value()) {
QWARN("filename index status unavailable (no local index) - skipping runtime check");
return;
}

// 定义有效状态列表(小写)
// 新状态映射返回 "scanning"/"updating"/"monitoring"("loading"/"closed" 不再出现)
static const QSet<QString> validStatuses {
"loading",
"scanning",
"monitoring",
"closed"
"updating",
"monitoring"
};

// 状态有效性检查
Expand All @@ -261,6 +264,89 @@
qInfo() << "Test Passed. Current anything status:" << currentStatus;
}

void tst_SearchUtils::testFileNameIndexStatusMapping()
{
struct Case {
QString name;
QByteArray json;
std::optional<QString> expectedStatus;
bool expectedReady;
};

const QList<Case> cases = {
// 首次创建中(Create 启动时 removeIndexStatusFile,最小字段)
{ "create-in-progress",
R"({"createInProgress": true})",
QStringLiteral("scanning"), false },
// 中断后继续创建(版本重建,无 lastUpdateTime)
{ "create-resumed",
R"({"createInProgress": true, "version": 1})",
QStringLiteral("scanning"), false },
// dirty 重启后的恢复 Update 中(索引滞后不可信 → 降级)
{ "recovery-update",
R"({"state": "dirty", "createInProgress": false, "updateInProgress": true, "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("scanning"), false },
// 运行中 needsRebuild 触发的 rebuild Update 中(新路径文件未索引 → 降级)
{ "rebuild-update",
R"({"state": "dirty", "updateInProgress": true, "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("scanning"), false },
// Update 失败/中断后标志保持(持续降级直到恢复成功)
{ "update-failed-persisted",
R"({"state": "dirty", "updateInProgress": true, "needsRebuild": true, "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("scanning"), false },
// 大规模事件积压处理中(backlogExceeded → "updating" → 降级 Realtime)
{ "backlog-exceeded",
R"({"state": "dirty", "backlogExceeded": true, "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("updating"), false },
// 崩溃时正在积压(dirty + backlog 残留,启动时保持 → 持续降级直到恢复完成)
{ "backlog-crash-residue",
R"({"state": "dirty", "backlogExceeded": true, "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("updating"), false },
// 积压处理完成(backlogExceeded 清除 → 恢复索引搜索)
{ "backlog-resolved",
R"({"state": "clean", "backlogExceeded": false, "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("monitoring"), true },
// 普通事件增量(dirty 但无恢复/积压标志,索引可用)
{ "incremental-dirty",
R"({"state": "dirty", "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("monitoring"), true },
// 恢复/重建/积压处理完成
{ "clean",
R"({"state": "clean", "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("monitoring"), true },
// 从未成功完成过任务(无 lastUpdateTime、无进行中标志)
{ "never-completed",
R"({"state": "dirty"})",
QStringLiteral("scanning"), false },
// 双标志同时置位(createInProgress 优先级最高)
{ "both-flags",
R"({"createInProgress": true, "updateInProgress": true, "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("scanning"), false },
// backlog + update 同时置位(updateInProgress 优先级高于 backlogExceeded)
{ "update-plus-backlog",
R"({"updateInProgress": true, "backlogExceeded": true, "lastUpdateTime": "2026-09-18T10:00:00"})",
QStringLiteral("scanning"), false },
// JSON 无效
{ "invalid-json",
"not-json",
std::nullopt, false },
// 空内容
{ "empty-content",
"",
std::nullopt, false },
};

for (const Case &c : cases) {
const auto actualStatus = Global::fileNameIndexStatusFromJson(c.json);
QVERIFY2(actualStatus == c.expectedStatus,
qPrintable(QString("case '%1': status mismatch, got %2")
.arg(c.name,
actualStatus.has_value() ? actualStatus.value() : QStringLiteral("nullopt"))));
QVERIFY2(Global::fileNameIndexReadyForSearchFromJson(c.json) == c.expectedReady,
qPrintable(QString("case '%1': ready mismatch").arg(c.name)));
}
}

void tst_SearchUtils::doFileNameBlacklistMatchTest(const QString &caseName,
const QString &inputPath,
const QStringList &blacklistEntries,
Expand Down
20 changes: 12 additions & 8 deletions include/dfm-search/dfm-search/dsearch_global.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,26 @@ bool isFileNameIndexDirectoryAvailable();

/**
* @brief Check if the filename index is ready for search operations.
* This function checks both the physical existence of the index and its status to ensure
* it's in the "monitoring" state and ready for search operations.
* Ready when the index physically exists, lastUpdateTime is present in
* index_status.json, and none of createInProgress, updateInProgress, or
* backlogExceeded is set (full build / recovery / rebuild / large event
* backlog in progress means the index is not trustworthy). The dirty/clean
* state is not checked: ordinary incremental updates keep the index searchable.
* @return True if the filename index is ready for search, false otherwise.
*/
bool isFileNameIndexReadyForSearch();

/**
* @brief Returns the current indexing status of the file name database.
* @brief Returns the current indexing status of the file name index,
* mapped from index_status.json (legacy semantics preserved).
*
* Possible status values:
* - "loading" : Initial loading of existing index
* - "scanning" : Actively scanning filesystem for changes
* - "monitoring" : Scan complete, now watching for filesystem events
* - "closed" : Normal shutdown state (anything terminated properly)
* - std::nullopt : index missing or status unavailable
* - "scanning" : full build / recovery / rebuild in progress (index not trustworthy)
* - "updating" : large event backlog in progress (index severely lagged)
* - "monitoring" : index usable (ordinary incremental updates may still be running)
*
* @return QString The current status string (lowercase)
* @return The current status string (lowercase), or nullopt if unavailable
*/
std::optional<QString> fileNameIndexStatus();

Expand Down
26 changes: 26 additions & 0 deletions include/dfm-search/dfm-search/filenamesearchapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,32 @@ class FileNameResultAPI
*/
qint64 fileSizeBytes() const;

// ==================== Pinyin (filename index) ====================

/**
* @brief Get the pinyin (full spelling) of the file name
* @return Pinyin string, empty if not stored in the index
*/
QString pinyin() const;

/**
* @brief Set the pinyin (full spelling) of the file name
* @param pinyin The pinyin string
*/
void setPinyin(const QString &pinyin);

/**
* @brief Get the pinyin acronym (initials) of the file name
* @return Pinyin acronym string, empty if not stored in the index
*/
QString pinyinAcronym() const;

/**
* @brief Set the pinyin acronym (initials) of the file name
* @param acronym The pinyin acronym string
*/
void setPinyinAcronym(const QString &acronym);

private:
SearchResult &m_result;
};
Expand Down
10 changes: 10 additions & 0 deletions src/dfm-search/dfm-search-client/output/json_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ QJsonValue JsonOutput::resultToJson(const SearchResult &result)

obj["isHidden"] = resultAPI.isHidden();

// 拼音(文件名索引字段,非拼音索引为空)
QString pinyin = resultAPI.pinyin();
if (!pinyin.isEmpty()) {
obj["pinyin"] = pinyin;
}
QString pinyinAcronym = resultAPI.pinyinAcronym();
if (!pinyinAcronym.isEmpty()) {
obj["pinyinAcronym"] = pinyinAcronym;
}

// 修改时间(包含时间戳和时间字符串)
qint64 modifyTs = resultAPI.modifyTimestamp();
if (modifyTs > 0) {
Expand Down
10 changes: 10 additions & 0 deletions src/dfm-search/dfm-search-client/output/text_output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ void TextOutput::printSearchResult(const SearchResult &result)
// 隐藏状态
std::cout << " Hidden: " << (resultAPI.isHidden() ? "Yes" : "No") << std::endl;

// 拼音(文件名索引字段,非拼音索引为空)
QString pinyin = resultAPI.pinyin();
if (!pinyin.isEmpty()) {
std::cout << " Pinyin: " << pinyin.toStdString() << std::endl;
}
QString pinyinAcronym = resultAPI.pinyinAcronym();
if (!pinyinAcronym.isEmpty()) {
std::cout << " Pinyin acronym: " << pinyinAcronym.toStdString() << std::endl;
}

// 修改时间(同时输出时间戳和时间字符串)
qint64 modifyTs = resultAPI.modifyTimestamp();
if (modifyTs > 0) {
Expand Down
22 changes: 22 additions & 0 deletions src/dfm-search/dfm-search-lib/filenamesearch/filenamesearchapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,26 @@ qint64 FileNameResultAPI::fileSizeBytes() const
return m_result.customAttribute("fileSizeBytes").toLongLong();
}

// ==================== Pinyin (filename index) ====================

QString FileNameResultAPI::pinyin() const
{
return m_result.customAttribute("pinyin").toString();
}

void FileNameResultAPI::setPinyin(const QString &pinyin)
{
m_result.setCustomAttribute("pinyin", pinyin);
}

QString FileNameResultAPI::pinyinAcronym() const
{
return m_result.customAttribute("pinyinAcronym").toString();
}

void FileNameResultAPI::setPinyinAcronym(const QString &acronym)
{
m_result.setCustomAttribute("pinyinAcronym", acronym);
}

DFM_SEARCH_END_NS
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,10 @@ SearchResult FileNameIndexedStrategy::processDetailedSearchResult(
}
}

// 拼音与拼音首字母
api.setPinyin(QString::fromStdWString(doc->get(LuceneFieldNames::FileName::kPinyin)));
api.setPinyinAcronym(QString::fromStdWString(doc->get(LuceneFieldNames::FileName::kPinyinAcronym)));

return result;
}

Expand Down
Loading
Loading