{"record":{"id":"01a0d014946622f9","repo":"siyuan-note/siyuan","slug":"s-ocr-relink","errorCode":null,"errorMessage":"%s","messagePattern":"%s","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/ocr_relink.go","lineNumber":19,"sourceCode":"// SiYuan - From thought to insight, with agents\n// Copyright (c) 2020-present, b3log.org\n// SPDX-License-Identifier: AGPL-3.0-or-later\n\npackage util\n\nimport (\n\t\"errors\"\n)\n\n// CopyAssetTextForRelink 复制 OCR 元数据，保留源资源的识别结果，并拒绝覆盖不同的目标结果。\nfunc CopyAssetTextForRelink(oldPath, newPath, historyDir string, dryRun bool) (exists bool, err error) {\n\tplan, err := PrepareAssetTextRelinks([]AssetTextRelinkMapping{{OldPath: oldPath, NewPath: newPath}})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tresult := plan.Results[0]\n\tif result.Reason != \"\" {\n\t\treturn result.Exists, errors.New(result.Reason)\n\t}\n\tif !dryRun {\n\t\t_, err = plan.Save(historyDir, []int{0})\n\t}\n\treturn result.Exists, err\n}\n","sourceCodeStart":1,"sourceCodeEnd":26,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/ocr_relink.go#L1-L26","documentation":"CopyAssetTextForRelink performs a pre-checked relink of OCR metadata from an old asset path to a new one. If the pre-check (PrepareAssetTextRelinks) records a Reason on the result — e.g. ocr_source_conflict or ocr_target_conflict — the copy is refused and that reason is surfaced as the returned error. This means relinking would overwrite or merge inconsistent OCR recognition results, so the library aborts to preserve metadata integrity.","triggerScenarios":"Calling CopyAssetTextForRelink(oldPath, newPath, historyDir, dryRun) when (a) the old path's OCR text conflicts with other entries keyed to the same canonical path (ocr_source_conflict), or (b) the new path already exists in the index with a different OCR value or is itself conflicted / is the merge target of sources with differing values (ocr_target_conflict).","commonSituations":"Renaming assets after an OCR re-run left stale keys under both the raw and canonical forms of a path; renaming an asset to a name that previously held different OCR text (e.g. after restoring a backup); batch renames where multiple sources funnel into one target with divergent recognition results.","solutions":["Inspect the error string: it is exactly the plan's Reason (ocr_source_conflict or ocr_target_conflict).","For ocr_source_conflict, clean up data/assets/ocr-texts.json so the old path has a single consistent OCR value, then retry.","For ocr_target_conflict, pick a different NewPath or manually reconcile the OCR text for the target in ocr-texts.json before relinking.","Use dryRun=true first and inspect the returned Reason before attempting a real copy.","If the old asset has no OCR entry, this path returns exists=false with no error; only handle the error when Reason applies."],"exampleFix":"// before: blind copy\nexists, err := util.CopyAssetTextForRelink(oldPath, newPath, historyDir, false)\n// after: dry-run pre-check and handle conflicts\nif exists, err := util.CopyAssetTextForRelink(oldPath, newPath, historyDir, true); err != nil {\n    log.Warnf(\"OCR relink refused (%s)\", err) // e.g. ocr_target_conflict\n} else if exists {\n    util.CopyAssetTextForRelink(oldPath, newPath, historyDir, false)\n}","handlingStrategy":"validation","validationCode":"// Pre-check with dryRun before committing\nexists, err := util.CopyAssetTextForRelink(oldPath, newPath, historyDir, true)\nif err != nil {\n    // Reason is ocr_source_conflict or ocr_target_conflict — surface to user, do not retry blindly\n    return fmt.Errorf(\"OCR relink pre-check failed: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if _, err := util.CopyAssetTextForRelink(old, new, histDir, false); err != nil {\n    switch {\n    case strings.Contains(err.Error(), \"ocr_source_conflict\"):\n        // clean source keys in ocr-texts.json, then retry\n    case strings.Contains(err.Error(), \"ocr_target_conflict\"):\n        // pick another NewPath or reconcile target text\n    default:\n        return err\n    }\n}","preventionTips":["Always dryRun=true first and inspect the Reason before the real copy","Keep ocr-texts.json free of duplicate/conflicting keys for the same asset","Avoid renaming assets to names that previously held different OCR text","Check result.Exists: relinking an asset with no OCR entry never errors, so treat error as conflict only"],"tags":["ocr","asset-relink","conflict","metadata"],"backgroundTag":"conflicting-config-options","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}