fix(reader): merge duplicate content records to restore bookmarks - #367
Conversation
Reviewer's GuideRestores bookmarks and reading progress across duplicate document paths by merging validated same-content records, safely resolving destination-path conflicts during fallback migration, and covering the key move, replacement, docId, bookmark, and timestamp scenarios with database tests. Sequence diagram for merging duplicate document records after a validated readsequenceDiagram
participant Reader
participant Database
participant OperationDB
participant BookmarkDB
Reader->>Database: readOperation(sheet)
Database->>OperationDB: Verify record at newPath
Database->>Database: mergeDuplicateRecords(sheet)
Database->>OperationDB: Find records at other paths by docId or fileSize+contentHash
loop Each duplicate path
Database->>BookmarkDB: migrateBookmarksToPath(oldPath, newPath, contentHash)
BookmarkDB-->>Database: Merge valid bookmarks by page
Database->>OperationDB: Prefer bookmarked state or newer lastOpened
Database->>OperationDB: Delete oldPath operation record
end
Database-->>Reader: Restored bookmarks and reading state
Sequence diagram for safe content-match path migrationsequenceDiagram
participant Reader
participant Database
participant OperationDB
participant BookmarkDB
Reader->>Database: matchOperationByContent(fileInfo, sheet)
Database->>OperationDB: Find matching record excluding target filePath
Database->>OperationDB: DELETE target-path stale operation record
Database->>OperationDB: UPDATE oldPath to newPath
Database->>BookmarkDB: migrateBookmarksToPath(oldPath, newPath, contentHash)
BookmarkDB-->>Database: Deduplicated valid bookmarks
Database-->>Reader: Commit migrated operation and bookmarks
Entity relationship diagram for merged operation and bookmark recordserDiagram
OPERATION {
string filePath PK
string docId
string contentHash
int fileSize
int currentPage
int lastOpened
}
BOOKMARK {
string filePath
int bookmarkIndex
string contentHash
}
OPERATION ||--o{ BOOKMARK : has
OPERATION }o--|| OPERATION : same_content_merge_target
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
deepin pr auto review🤖 AI 代码审查报告📊 总体评价
🔍 详细分析1. 语法逻辑 ✅评价: 优秀 ✅ 通过 潜在问题: 建议: 语法正确,逻辑清晰。mergeDuplicateRecords 方法正确处理同内容多路径记录合并:先查询当前路径记录的内容特征,再查找同内容(docId 或 fileSize+contentHash)的其他路径记录,通过 migrateBookmarksToPath 进行书签并集迁移去重,状态择优逻辑合理(带书签记录优先,否则取 lastOpened 较新者)。matchOperationByContent 修改正确:排除目标路径自身避免无效迁移,迁移前清理目标路径旧记录避让主键冲突。边界处理完善:sheet 空指针检查、contentHash 空值跳过、scaleFactor 使用 qBound 边界约束、JSON 解析使用 isArray 校验。 2. 代码质量 ❌评价: 良好 ❌ 不通过 潜在问题:
建议: 结构需要优化。建议将 mergeDuplicateRecords 中 sheet->m_operation 的内存状态同步代码(layoutMode、mouseShape、scaleMode 等字段赋值)提取为独立的 syncOperationToMemory 辅助函数,降低主函数长度。其余方面:注释完整性优秀(Doxygen 风格 + 行内解释决策原因),代码复用良好(migrateBookmarksToPath 被 matchOperationByContent 和 mergeDuplicateRecords 两处复用),无残留调试代码。 3. 代码性能 ✅评价: 优秀 ✅ 通过 潜在问题: 建议: 性能良好,资源使用合理。SQL 查询使用 filePath 主键索引,contentHash/fileSize 字段查询高效。migrateBookmarksToPath 使用 INSERT...SELECT...NOT EXISTS 单语句完成去重迁移,避免多次查询。所有操作在单个事务中执行保证原子性。mergeDuplicateRecords 中的循环次数取决于重复记录数(实际场景通常0-2条),无性能瓶颈。readOperation 中新增的 mergeDuplicateRecords 调用仅在指纹校验通过后执行,不影响正常打开流程。 4. 代码安全 🔒评价: 优秀 ✅ 通过
安全漏洞详情: 建议: 存在0个安全漏洞,安全合规。全部 SQL 语句使用参数化查询(prepare/bindValue),无 SQL 注入风险。文件路径来源于 sheet->filePath() 和 fileInfo.absoluteFilePath(),为应用内部路径,无用户直接输入注入。日志仅输出路径和计数,无敏感信息泄露。事务保证数据一致性,错误处理使用 qCWarning 记录但不暴露系统信息。测试代码中有一处 SQL 字符串拼接(ut_database.cpp:495-496),但值为测试常量路径,无安全风险。 💡 改进建议代码示例// 建议将 mergeDuplicateRecords 中的内存状态同步提取为辅助函数
void Database::syncOperationToMemory(DocSheet *sheet, const QVariantMap &dup)
{
sheet->m_operation.layoutMode = static_cast<Dr::LayoutMode>(dup.value("layoutMode").toInt());
sheet->m_operation.mouseShape = static_cast<Dr::MouseShape>(dup.value("mouseShape").toInt());
sheet->m_operation.scaleMode = static_cast<Dr::ScaleMode>(dup.value("scaleMode").toInt());
sheet->m_operation.rotation = static_cast<Dr::Rotation>(dup.value("rotation").toInt());
sheet->m_operation.scaleFactor = qBound(0.1, dup.value("scaleFactor").toDouble(), 5.0);
sheet->m_operation.sidebarVisible = dup.value("sidebarVisible").toInt();
sheet->m_operation.sidebarIndex = dup.value("sidebarIndex").toInt();
sheet->m_operation.currentPage = dup.value("currentPage").toInt();
sheet->m_operation.sidebarWidth = dup.value("sidebarWidth").toInt();
sheet->m_operation.sidebarWidthChanged = dup.value("sidebarWidthChanged").toInt() != 0;
sheet->m_operation.scrollPosition = dup.value("scrollPosition").toFloat();
QString expandedJson = dup.value("expandedSections").toString();
QJsonDocument expDoc = QJsonDocument::fromJson(expandedJson.toUtf8());
if (expDoc.isArray()) {
sheet->m_operation.expandedSections.clear();
for (const QJsonValue &val : expDoc.array()) {
sheet->m_operation.expandedSections.append(val.toString());
}
}
}
// 在 mergeDuplicateRecords 中调用:
// syncOperationToMemory(sheet, dup);
// curLastOpened = dupLastOpened;本报告由 AI 代码审查工具自动生成 |
3e4b956 to
f1fac97
Compare
| idQuery.prepare("SELECT * FROM operation WHERE docId = :docId AND docId != ''"); | ||
| // 排除目标路径自身的记录:同一份文件可能在目标路径已有旧记录, | ||
| // 选中它会导致 oldPath == newPath,做一次无效迁移 | ||
| idQuery.prepare("SELECT * FROM operation WHERE docId = :docId AND docId != '' AND filePath != :filePath"); |
f1fac97 to
2cbab25
Compare
When the same document exists at multiple paths, readOperation may hit a stale record at the new path with a matching contentHash and skip matchOperationByContent, so bookmarks and reading progress stored at the old path are never migrated. Migrating to a path that already has a record also fails with a PRIMARY KEY conflict on operation.filePath. Fix by merging same-content records from other paths after a verified readOperation hit: union bookmarks (dedup by page, filter mismatched hashes), prefer the state that has bookmarks (else the latest lastOpened), and delete the old-path records. In matchOperationByContent, exclude the target path itself from candidates and clear its stale record before the path UPDATE. 修复文档移动到已有同内容记录的路径后书签与阅读进度丢失的问题: readOperation 命中旧记录且指纹校验通过后,合并同内容(docId 或 fileSize+contentHash)的其他路径记录——书签并集迁移(按页去重并 过滤指纹不符的脏书签)、状态择优(带书签记录优先,否则取 lastOpened 较新者)并清理旧路径记录;matchOperationByContent 匹配候选排除目标 路径自身,迁移 UPDATE 前清理目标路径旧记录以避让主键冲突。 Log: 修复文档移动到U盘替换后书签与阅读进度丢失 PMS: BUG-376029 Influence: 本地与U盘存在同名文档时,添加书签后移动/替换文档再打开,书签与阅读进度可正常恢复。
2cbab25 to
f7f4b13
Compare
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: add-uos, lzwind The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
/merge |
When the same document exists at multiple paths, readOperation may hit a stale record at the new path with a matching contentHash and skip matchOperationByContent, so bookmarks and reading progress stored at the old path are never migrated. Migrating to a path that already has a record also fails with a PRIMARY KEY conflict on operation.filePath.
Fix by merging same-content records from other paths after a verified readOperation hit: union bookmarks (dedup by page, filter mismatched hashes), prefer the state that has bookmarks (else the latest lastOpened), and delete the old-path records. In
matchOperationByContent, exclude the target path itself from candidates and clear its stale record before the path UPDATE.
修复文档移动到已有同内容记录的路径后书签与阅读进度丢失的问题:
readOperation 命中旧记录且指纹校验通过后,合并同内容(docId 或
fileSize+contentHash)的其他路径记录——书签并集迁移(按页去重并
过滤指纹不符的脏书签)、状态择优(带书签记录优先,否则取 lastOpened
较新者)并清理旧路径记录;matchOperationByContent 匹配候选排除目标
路径自身,迁移 UPDATE 前清理目标路径旧记录以避让主键冲突。
Log: 修复文档移动到U盘替换后书签与阅读进度丢失
PMS: BUG-376029
Influence: 本地与U盘存在同名文档时,添加书签后移动/替换文档再打开,书签与阅读进度可正常恢复。
Summary by Sourcery
Restore document reading state across moves and replacements while keeping independent copies isolated and cleaning up obsolete bookmark data.
Bug Fixes:
Enhancements:
Tests: