{"record":{"id":"c91d079c30937596","repo":"siyuan-note/siyuan","slug":"314-conf-language","errorCode":"314","errorMessage":"Conf.Language(314)","messagePattern":"Conf\\.Language\\(314\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/asset_download.go","lineNumber":385,"sourceCode":"}\n\n// readRepoFileWithAssets 将仓库实例的创建和历史分块补齐纳入来源读锁。\nfunc readRepoFileWithAssets(fileID string) ([]byte, *entity.File, error) {\n\tassetDownloadSourceMu.RLock()\n\tdefer assetDownloadSourceMu.RUnlock()\n\tif Conf.Repo == nil || len(Conf.Repo.Key) == 0 {\n\t\treturn nil, nil, errors.New(Conf.Language(26))\n\t}\n\trepo, err := newRepositoryWithAssetSourceLocked()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tfile, err := repo.GetFile(fileID)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif boxID := encryptedBoxIDFromRepoPath(file.Path); boxID != \"\" && !IsBoxUnlocked(boxID) {\n\t\treturn nil, nil, errors.New(Conf.Language(314))\n\t}\n\tdata, err := openRepoFileWithAssets(repo, file)\n\treturn data, file, err\n}\n\nfunc ensureRepoSnapshotComplete(repo *dejavu.Repo, indexID string) error {\n\tindex, err := repo.GetIndex(indexID)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn repo.GetFilesIter(index, func(file *entity.File) error {\n\t\tif !repoFileNeedsDownload(file) {\n\t\t\treturn nil\n\t\t}\n\t\tif err := checkAssetDownloadAccess(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\thandleCloudError := cloudRepoErrorHandler()","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/asset_download.go#L367-L403","documentation":"After locating the repo file, readRepoFileWithAssets checks whether the file's path belongs to an encrypted notebook (encryptedBoxIDFromRepoPath) and, if that notebook is currently locked, aborts with i18n message 314 ('Please unlock the encrypted notebook first'). Encrypted notebooks must be unlocked so their keys are in memory before any snapshot content can be legitimately read.","triggerScenarios":"GetRepoFile / OpenRepoSnapshotFile / RollbackRepoSnapshotFile / ExportRepoFile / TestAssetDownloadModePreservesHistoricalRecovery reading a file whose repo path maps to an encrypted box while the box is locked (user has not entered the notebook passphrase since kernel start, or locked it manually).","commonSituations":"Accessing history/versions of an encrypted notebook after kernel restart (lock state is not persisted); automated export scripts running without the notebook unlocked; user locked the encrypted notebook and then tried to view an old asset or snapshot.","solutions":["Unlock the encrypted notebook (enter its passphrase) and retry the read/export","Unlock programmatically via the box unlock API before calling repo-file APIs in scripts","Route the request through the UI, which prompts for unlock instead of failing","Verify with IsBoxUnlocked(boxID) before issuing the call"],"exampleFix":"// before\ndata, err := model.GetRepoFile(fileID)\n// after\nif boxID := model.BoxIDOfRepoFile(fileID); boxID != \"\" && !model.IsBoxUnlocked(boxID) {\n    return errors.New(model.Conf.Language(314)) // prompt user to unlock\n}\ndata, err := model.GetRepoFile(fileID)","handlingStrategy":"validation","validationCode":"if boxID != \"\" && model.IsEncryptedBox(boxID) && !model.IsBoxUnlocked(boxID) {\n    return errors.New(model.Conf.Language(314))\n}","typeGuard":null,"tryCatchPattern":"data, err := model.GetRepoFile(fileID)\nvar unlockErr *model.EncryptedBoxLockedError\nif errors.As(err, &unlockErr) {\n    promptUserToUnlock(unlockErr.BoxID)\n}","preventionTips":["Unlock encrypted notebooks right after kernel start in workflows that read history","Persist a pre-flight IsBoxUnlocked check in scripts touching repo files","Avoid mixing encrypted notebooks into automated read/export jobs without unlock handling","Surface the unlock prompt in UI before exposing history/version features"],"tags":["encrypted-notebook","lock","access-control","i18n"],"backgroundTag":"authentication-required","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"}