From 9491029bac51caa33d943460339ade7963b6d97b Mon Sep 17 00:00:00 2001 From: Zhang Sheng Date: Fri, 18 Sep 2026 17:14:47 +0800 Subject: [PATCH] feat: enhance filename index status handling and pinyin output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extended filename search capabilities with improved index status evaluation and pinyin metadata support. Updated index status handling to better distinguish between scanning/monitoring states, ensuring search reliability during various index operations. Added pinyin and pinyin acronym fields to search results when available from the filename index. Changes: 1. Enhanced index status evaluation with new logic focusing on createInProgress and updateInProgress flags 2. Renamed status.json to index_status.json and updated path to use QStandardPaths 3. Added pinyin and pinyin acronym getters/setters to FileNameResultAPI 4. Updated indexed strategy to extract pinyin data from Lucene documents 5. Enhanced JSON and text output formats to include pinyin metadata when available 6. Added comprehensive test cases for pinyin output and index status mapping Log: Added pinyin metadata to search results and improved index status reliability detection Influence: 1. Test search results with Chinese filenames to verify pinyin metadata appears in JSON/text output 2. Verify pinyin fields only appear when detailed results are enabled 3. Test that plain filename results without pinyin do not output empty pinyin keys 4. Verify index status accurately reflects scanning vs monitoring states during index operations 5. Test search behavior during index creation/recovery operations to ensure proper fallback 6. Validate that index status unavailable warning appears when no local index exists feat: 增强文件名索引状态处理和拼音输出 扩展了文件名搜索功能,改进了索引状态评估和拼音元数据支持。更新了索引状态 处理逻辑,更好地区分扫描/监控状态,确保在各种索引操作期间的搜索可靠性。 当文件名索引中可用时,搜索结果中添加拼音和拼音首字母字段。 变更内容: 1. 增强索引状态评估逻辑,重点关注 createInProgress 和 updateInProgress 标志 2. 将 status.json 重命名为 index_status.json 并使用 QStandardPaths 更新 路径 3. 为 FileNameResultAPI 添加拼音和拼音首字母的getter/setter方法 4. 更新索引策略以从Lucene文档中提取拼音数据 5. 增强JSON和文本输出格式,当可用时包含拼音元数据 6. 为拼音输出和索引状态映射添加全面的测试用例 Log: 在搜索结果中添加拼音元数据并改进索引状态可靠性检测 Influence: 1. 测试包含中文文件名的搜索结果,验证拼音元数据是否出现在JSON/文本输出中 2. 验证拼音字段仅在详细结果启用时出现 3. 测试没有拼音的普通文件名结果不会输出空的拼音键 4. 验证索引状态在索引操作期间准确反映扫描与监控状态 5. 测试索引创建/恢复操作期间的搜索行为,确保正确的回退机制 6. 验证当没有本地索引时会出现索引状态不可用警告 --- .../dfm-search-tests/tst_search_output.cpp | 66 +++++++++ .../dfm-search-tests/tst_search_utils.cpp | 98 ++++++++++++- .../dfm-search/dfm-search/dsearch_global.h | 20 +-- .../dfm-search/dfm-search/filenamesearchapi.h | 26 ++++ .../dfm-search-client/output/json_output.cpp | 10 ++ .../dfm-search-client/output/text_output.cpp | 10 ++ .../filenamesearch/filenamesearchapi.cpp | 22 +++ .../filenamestrategies/indexedstrategy.cpp | 4 + .../dfm-search-lib/utils/searchutility.cpp | 133 ++++++++++-------- .../dfm-search-lib/utils/searchutility.h | 31 ++++ 10 files changed, 351 insertions(+), 69 deletions(-) diff --git a/autotests/dfm-search-tests/tst_search_output.cpp b/autotests/dfm-search-tests/tst_search_output.cpp index 39107504..8515d3e4 100644 --- a/autotests/dfm-search-tests/tst_search_output.cpp +++ b/autotests/dfm-search-tests/tst_search_output.cpp @@ -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() @@ -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(); diff --git a/autotests/dfm-search-tests/tst_search_utils.cpp b/autotests/dfm-search-tests/tst_search_utils.cpp index c962d449..73ab169e 100644 --- a/autotests/dfm-search-tests/tst_search_utils.cpp +++ b/autotests/dfm-search-tests/tst_search_utils.cpp @@ -16,6 +16,7 @@ #include #include #include +#include using namespace DFMSEARCH; @@ -30,6 +31,7 @@ private Q_SLOTS: void testPinyin(); void testPinyinAcronym(); void testAnythingStatus(); + void testFileNameIndexStatusMapping(); void testFileNameBlacklistMatcher(); void testNGramSearchQuery(); @@ -238,15 +240,16 @@ void tst_SearchUtils::testAnythingStatus() // 测试状态获取 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 validStatuses { - "loading", "scanning", - "monitoring", - "closed" + "updating", + "monitoring" }; // 状态有效性检查 @@ -261,6 +264,89 @@ void tst_SearchUtils::testAnythingStatus() qInfo() << "Test Passed. Current anything status:" << currentStatus; } +void tst_SearchUtils::testFileNameIndexStatusMapping() +{ + struct Case { + QString name; + QByteArray json; + std::optional expectedStatus; + bool expectedReady; + }; + + const QList 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, diff --git a/include/dfm-search/dfm-search/dsearch_global.h b/include/dfm-search/dfm-search/dsearch_global.h index 37a4c582..63e70ebb 100644 --- a/include/dfm-search/dfm-search/dsearch_global.h +++ b/include/dfm-search/dfm-search/dsearch_global.h @@ -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 fileNameIndexStatus(); diff --git a/include/dfm-search/dfm-search/filenamesearchapi.h b/include/dfm-search/dfm-search/filenamesearchapi.h index 4093f34d..5fe7f2b9 100644 --- a/include/dfm-search/dfm-search/filenamesearchapi.h +++ b/include/dfm-search/dfm-search/filenamesearchapi.h @@ -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; }; diff --git a/src/dfm-search/dfm-search-client/output/json_output.cpp b/src/dfm-search/dfm-search-client/output/json_output.cpp index e46871e4..c50db243 100644 --- a/src/dfm-search/dfm-search-client/output/json_output.cpp +++ b/src/dfm-search/dfm-search-client/output/json_output.cpp @@ -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) { diff --git a/src/dfm-search/dfm-search-client/output/text_output.cpp b/src/dfm-search/dfm-search-client/output/text_output.cpp index 33168268..afb2f8aa 100644 --- a/src/dfm-search/dfm-search-client/output/text_output.cpp +++ b/src/dfm-search/dfm-search-client/output/text_output.cpp @@ -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) { diff --git a/src/dfm-search/dfm-search-lib/filenamesearch/filenamesearchapi.cpp b/src/dfm-search/dfm-search-lib/filenamesearch/filenamesearchapi.cpp index c47de316..18bbe3e5 100644 --- a/src/dfm-search/dfm-search-lib/filenamesearch/filenamesearchapi.cpp +++ b/src/dfm-search/dfm-search-lib/filenamesearch/filenamesearchapi.cpp @@ -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 diff --git a/src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp b/src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp index 0119d216..1bad5a39 100644 --- a/src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp +++ b/src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp @@ -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; } diff --git a/src/dfm-search/dfm-search-lib/utils/searchutility.cpp b/src/dfm-search/dfm-search-lib/utils/searchutility.cpp index 59318a00..ea159583 100644 --- a/src/dfm-search/dfm-search-lib/utils/searchutility.cpp +++ b/src/dfm-search/dfm-search-lib/utils/searchutility.cpp @@ -738,87 +738,110 @@ bool isFileNameIndexDirectoryAvailable() } } -bool isFileNameIndexReadyForSearch() +namespace { + +struct FileNameIndexStatusFields { + bool parsed { false }; + bool createInProgress { false }; + bool updateInProgress { false }; + bool backlogExceeded { false }; + QString lastUpdateTime; +}; + +FileNameIndexStatusFields parseFileNameIndexStatusJson(const QByteArray &jsonContent) { - // First check if the index physically exists - if (!isFileNameIndexDirectoryAvailable()) { - qDebug() << "Index directory does not exist physically."; - return false; - } + FileNameIndexStatusFields fields; - // Then check the status to ensure it's in monitoring state - std::optional currentStatus = fileNameIndexStatus(); - if (!currentStatus) { - qWarning() << "Failed to get file name index status."; - return false; - } + QJsonParseError jsonError; + const QJsonDocument doc = QJsonDocument::fromJson(jsonContent, &jsonError); + if (jsonError.error != QJsonParseError::NoError || !doc.isObject()) + return fields; - const QStringList &validStatus = { "monitoring" }; - const QString &status = currentStatus.value(); - if (!validStatus.contains(status)) { - qDebug() << "Index status is '" << status - << "', expected or 'monitoring'. Index not ready for search."; - return false; - } + const QJsonObject root = doc.object(); + fields.parsed = true; + fields.createInProgress = root.value("createInProgress").toBool(false); + fields.updateInProgress = root.value("updateInProgress").toBool(false); + fields.backlogExceeded = root.value("backlogExceeded").toBool(false); + fields.lastUpdateTime = root.value("lastUpdateTime").toString(); + return fields; +} - return true; +QByteArray readFileNameIndexStatusFile() +{ + const QString &statusFilePath = QDir(fileNameIndexDirectory()).filePath("index_status.json"); + QFile statusFile(statusFilePath); + if (!statusFile.exists() || !statusFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return {}; + return statusFile.readAll(); } -std::optional fileNameIndexStatus() +} // namespace + +std::optional fileNameIndexStatusFromJson(const QByteArray &jsonContent) { - if (!isFileNameIndexDirectoryAvailable()) { - qWarning() << "Index directory not available"; + const FileNameIndexStatusFields fields = parseFileNameIndexStatusJson(jsonContent); + if (!fields.parsed) return std::nullopt; - } - const QString &statusFilePath = QDir(fileNameIndexDirectory()).filePath("status.json"); - QFile statusFile(statusFilePath); + // 检查优先级:createInProgress > updateInProgress > backlogExceeded > state + // 全量创建/版本重建/断点续建中:索引滞后不可信 + if (fields.createInProgress || fields.updateInProgress) + return QStringLiteral("scanning"); - // 检查文件是否存在和可读 - if (!statusFile.exists()) { - qWarning() << "Status file does not exist:" << statusFilePath; - return std::nullopt; - } - if (!statusFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning() << "Failed to open status file:" << statusFile.errorString(); - return std::nullopt; - } + // 大规模事件积压处理中(复刻旧 updating 语义):索引严重滞后,降级 Realtime + if (fields.backlogExceeded) + return QStringLiteral("updating"); - // 读取和解析JSON - QJsonParseError jsonError; - const QJsonDocument doc = QJsonDocument::fromJson(statusFile.readAll(), &jsonError); - statusFile.close(); + // lastUpdateTime 只在任务成功后写入;普通增量(dirty)期间索引仍可搜索 + if (!fields.lastUpdateTime.isEmpty()) + return QStringLiteral("monitoring"); - if (jsonError.error != QJsonParseError::NoError) { - qWarning() << "JSON parse error:" << jsonError.errorString() - << "at offset:" << jsonError.offset; - return std::nullopt; - } + // 从未成功完成过任务 + return QStringLiteral("scanning"); +} - // 检查JSON结构 - if (!doc.isObject()) { - qWarning() << "Invalid JSON format: root is not an object"; - return std::nullopt; +bool fileNameIndexReadyForSearchFromJson(const QByteArray &jsonContent) +{ + const FileNameIndexStatusFields fields = parseFileNameIndexStatusJson(jsonContent); + return fields.parsed + && !fields.createInProgress + && !fields.updateInProgress + && !fields.backlogExceeded + && !fields.lastUpdateTime.isEmpty(); +} + +bool isFileNameIndexReadyForSearch() +{ + // 1. 索引物理存在 + if (!isFileNameIndexDirectoryAvailable()) { + qDebug() << "Index directory does not exist physically."; + return false; } - const QJsonObject root = doc.object(); - if (!root.contains("status") || !root["status"].isString()) { - qWarning() << "Missing or invalid 'status' field"; + // 2. lastUpdateTime 非空 3. !createInProgress 4. !updateInProgress 5. !backlogExceeded + // 不检查 dirty/clean state(普通增量期间索引仍可搜索) + return fileNameIndexReadyForSearchFromJson(readFileNameIndexStatusFile()); +} + +std::optional fileNameIndexStatus() +{ + if (!isFileNameIndexDirectoryAvailable()) { + qWarning() << "Index directory not available"; return std::nullopt; } - // 返回小写状态 - return root["status"].toString().toLower(); + return fileNameIndexStatusFromJson(readFileNameIndexStatusFile()); } QString fileNameIndexDirectory() { - return QString("/run/user/%1/deepin-anything-server").arg(getuid()); + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + + "/deepin/dde-file-manager/filename-index"; } int fileNameIndexVersion() { - return readIndexVersion(fileNameIndexDirectory(), "status.json"); + return readIndexVersion(fileNameIndexDirectory(), "index_status.json"); } int contentIndexVersion() diff --git a/src/dfm-search/dfm-search-lib/utils/searchutility.h b/src/dfm-search/dfm-search-lib/utils/searchutility.h index 9c76c862..15d87451 100644 --- a/src/dfm-search/dfm-search-lib/utils/searchutility.h +++ b/src/dfm-search/dfm-search-lib/utils/searchutility.h @@ -8,6 +8,7 @@ #include #include #include +#include #include // SPDX-FileCopyrightText: 2025 - 2026 UnionTech Software Technology Co., Ltd. @@ -21,6 +22,36 @@ DFM_SEARCH_BEGIN_NS /** * @brief The SearchUtility namespace provides utility functions for search operations */ +namespace Global { + +/** + * @brief Map filename index status JSON content (index_status.json) to the legacy status string + * + * Mapping (keeps legacy semantics): + * - std::nullopt : JSON missing/invalid + * - "scanning" : createInProgress or updateInProgress is set (full build / recovery / + * rebuild update in progress, index not trustworthy), or no successful + * task has ever completed (empty lastUpdateTime) + * - "updating" : backlogExceeded is set (large event backlog in progress, index + * severely lagged — replicates legacy "updating" semantics) + * - "monitoring" : lastUpdateTime present and no in-progress/backlog flags (index + * searchable, ordinary incremental updates may still be running) + */ +std::optional fileNameIndexStatusFromJson(const QByteArray &jsonContent); + +/** + * @brief Check whether index_status.json content marks the filename index ready for search + * + * Five conditions: valid JSON && lastUpdateTime non-empty && !createInProgress + * && !updateInProgress && !backlogExceeded. The dirty/clean state is not checked: + * ordinary incremental updates keep the index searchable, while recovery/rebuild + * updates and large event backlogs do set their respective flags and must degrade + * search to realtime traversal. + */ +bool fileNameIndexReadyForSearchFromJson(const QByteArray &jsonContent); + +} // namespace Global + namespace SearchUtility { /**