From 7a3d4a05ad4d65fb4013f33e36fe7f3f28cfd48f Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Thu, 20 Jun 2024 22:53:27 +0800 Subject: [PATCH] :recycle: Refactor the blocktree storage https://github.com/siyuan-note/siyuan/issues/11773 --- app/appearance/langs/en_US.json | 2 +- app/appearance/langs/es_ES.json | 2 +- app/appearance/langs/fr_FR.json | 2 +- app/appearance/langs/ja_JP.json | 2 +- app/appearance/langs/zh_CHT.json | 2 +- app/appearance/langs/zh_CN.json | 2 +- kernel/filesys/tree.go | 4 +- kernel/job/cron.go | 2 - kernel/model/assets.go | 2 +- kernel/model/box.go | 1 - kernel/model/conf.go | 27 +- kernel/model/file.go | 4 +- kernel/model/index_fix.go | 2 +- kernel/model/mount.go | 2 - kernel/model/sync.go | 2 +- kernel/model/transaction.go | 2 +- kernel/model/tree.go | 2 +- kernel/sql/database.go | 9 +- kernel/treenode/blocktree.go | 762 +++++++++++++++---------------- kernel/treenode/blocktree_fix.go | 91 ++-- kernel/util/working.go | 4 +- kernel/util/working_mobile.go | 2 +- 22 files changed, 443 insertions(+), 487 deletions(-) diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 4ab7010e65f..3f424b13da8 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1377,7 +1377,7 @@ "88": "Finished parsing [%d] data files, remaining to be processed [%d]", "89": "[%d/%d] Created [%d] of data indexes of block-level elements [%s]", "90": "[%d/%d] Created [%d] of search indexes of block-level elements [%s]", - "91": "Reading block tree data...", + "91": "TODO", "92": "Parsing document tree [%s]", "93": "[%d/%d] Cleaned up the index related to document [%s]", "94": "Upload failed: %s", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 4a5fa1f1953..4a2299803b8 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1377,7 +1377,7 @@ "88": "Se ha terminado de analizar [%d] archivos de datos, quedan por procesar [%d]", "89": "[%d/%d] Creado [%d] de índices de datos de elementos a nivel de bloque [%s]", "90": "[%d/%d] Creado [%d] de índices de búsqueda de elementos a nivel de bloque [%s]", - "91": "Leyendo datos del árbol de bloques...", + "91": "TODO", "92": "Analizando el árbol del documento [%s]", "93": "[%d/%d] ha limpiado el índice relacionado con el documento [%s]", "94": "Carga fallida: %s", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index 0dce7fba591..bd619e8e27e 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1377,7 +1377,7 @@ "88": "Fin de l'analyse des fichiers de données [%d], restant à traiter [%d]", "89": "[%d/%d] Créé [%d] d'index de données d'éléments de niveau bloc [%s]", "90": "[%d/%d] Création de [%d] index de recherche d'éléments de niveau bloc [%s]", - "91": "Lecture des données de l'arborescence des blocs...", + "91": "TODO", "92": "Analyse de l'arborescence du document [%s]", "93": "[%d/%d] a nettoyé l'index lié au document [%s]", "94": "Échec du téléchargement : %s", diff --git a/app/appearance/langs/ja_JP.json b/app/appearance/langs/ja_JP.json index 0d78e8a8059..dc02d91fcff 100644 --- a/app/appearance/langs/ja_JP.json +++ b/app/appearance/langs/ja_JP.json @@ -1377,7 +1377,7 @@ "88": "[%d] 個のデータファイルの解析が完了し、処理待ちのデータファイルが [%d] 個残っています", "89": "[%d/%d] ブロックレベル要素 [%s] のデータインデックスを [%d] 個作成しました", "90": "[%d/%d] ブロックレベル要素 [%s] の検索インデックスを [%d] 個作成しました", - "91": "ブロックツリーデータを読み込んでいます...", + "91": "TODO", "92": "ドキュメントツリーを解析しています [%s]", "93": "[%d/%d] ドキュメント [%s] に関連するインデックスをクリーンアップしました", "94": "アップロードに失敗しました: %s", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 68e7136ac66..073fe28ca20 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1377,7 +1377,7 @@ "88": "已完成解析 [%d] 個資料文件,剩餘待處理 [%d]", "89": "[%d/%d] 已經創建 [%d] 個塊級元素的資料索引 [%s]", "90": "[%d/%d] 已經創建 [%d] 個塊級元素的搜索索引 [%s]", - "91": "正在讀取塊樹資料...", + "91": "TODO", "92": "正在解析文檔樹 [%s]", "93": "[%d/%d] 已經清理文檔 [%s] 相關的索引", "94": "上傳失敗:%s", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 002f66effd6..a28ec8c9e68 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1377,7 +1377,7 @@ "88": "已完成解析 [%d] 个数据文件,剩余待处理 [%d]", "89": "[%d/%d] 已经创建 [%d] 个块级元素的数据索引 [%s]", "90": "[%d/%d] 已经创建 [%d] 个块级元素的搜索索引 [%s]", - "91": "正在读取块树数据...", + "91": "TODO", "92": "正在解析文档树 [%s]", "93": "[%d/%d] 已经清理文档 [%s] 相关的索引", "94": "上传失败:%s", diff --git a/kernel/filesys/tree.go b/kernel/filesys/tree.go index 16cd1c7076c..27ae1a036ca 100644 --- a/kernel/filesys/tree.go +++ b/kernel/filesys/tree.go @@ -88,7 +88,7 @@ func LoadTreeByData(data []byte, boxID, p string, luteEngine *lute.Lute) (ret *p logging.LogErrorf("rebuild parent tree [%s] failed: %s", parentAbsPath, writeErr) } else { logging.LogInfof("rebuilt parent tree [%s]", parentAbsPath) - treenode.IndexBlockTree(parentTree) + treenode.UpsertBlockTree(parentTree) } } else { logging.LogWarnf("read parent tree data [%s] failed: %s", parentAbsPath, readErr) @@ -137,7 +137,7 @@ func prepareWriteTree(tree *parse.Tree) (data []byte, filePath string, err error newP := treenode.NewParagraph() tree.Root.AppendChild(newP) tree.Root.SetIALAttr("updated", util.TimeFromID(newP.ID)) - treenode.IndexBlockTree(tree) + treenode.UpsertBlockTree(tree) } filePath = filepath.Join(util.DataDir, tree.Box, tree.Path) diff --git a/kernel/job/cron.go b/kernel/job/cron.go index d4c86ef2933..90db0d75f7c 100644 --- a/kernel/job/cron.go +++ b/kernel/job/cron.go @@ -23,14 +23,12 @@ import ( "github.com/siyuan-note/siyuan/kernel/model" "github.com/siyuan-note/siyuan/kernel/sql" "github.com/siyuan-note/siyuan/kernel/task" - "github.com/siyuan-note/siyuan/kernel/treenode" "github.com/siyuan-note/siyuan/kernel/util" ) func StartCron() { go every(100*time.Millisecond, task.ExecTaskJob) go every(5*time.Second, task.StatusJob) - go every(5*time.Second, treenode.SaveBlockTreeJob) go every(5*time.Second, model.SyncDataJob) go every(2*time.Hour, model.StatJob) go every(2*time.Hour, model.RefreshCheckJob) diff --git a/kernel/model/assets.go b/kernel/model/assets.go index aa79daa479f..ed51afd7286 100644 --- a/kernel/model/assets.go +++ b/kernel/model/assets.go @@ -825,7 +825,7 @@ func RenameAsset(oldPath, newName string) (err error) { continue } - treenode.IndexBlockTree(tree) + treenode.UpsertBlockTree(tree) sql.UpsertTreeQueue(tree) util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) diff --git a/kernel/model/box.go b/kernel/model/box.go index 4fffcd864ad..1f56065af59 100644 --- a/kernel/model/box.go +++ b/kernel/model/box.go @@ -531,7 +531,6 @@ func fullReindex() { for _, openedBox := range openedBoxes { index(openedBox.ID) } - treenode.SaveBlockTree(true) LoadFlashcards() debug.FreeOSMemory() } diff --git a/kernel/model/conf.go b/kernel/model/conf.go index 9a518967050..288eda500ff 100644 --- a/kernel/model/conf.go +++ b/kernel/model/conf.go @@ -627,7 +627,6 @@ func Close(force, setCurrentWorkspace bool, execInstallPkg int) (exitCode int) { Conf.Close() sql.CloseDatabase() - treenode.SaveBlockTree(false) util.SaveAssetsTexts() clearWorkspaceTemp() clearCorruptedNotebooks() @@ -818,24 +817,7 @@ func (conf *AppConf) language(num int) (ret string) { } func InitBoxes() { - initialized := false - if 1 > treenode.CountBlocks() { - if gulu.File.IsExist(util.BlockTreePath) { - util.IncBootProgress(20, Conf.Language(91)) - go func() { - for i := 0; i < 40; i++ { - util.RandomSleep(50, 100) - util.IncBootProgress(1, Conf.Language(91)) - } - }() - - treenode.InitBlockTree(false) - initialized = true - } - } else { // 大于 1 的话说明在同步阶段已经加载过了 - initialized = true - } - + initialized := 0 < treenode.CountBlocks() for _, box := range Conf.GetOpenedBoxes() { box.UpdateHistoryGenerated() // 初始化历史生成时间为当前时间 @@ -844,10 +826,6 @@ func InitBoxes() { } } - if !initialized { - treenode.SaveBlockTree(true) - } - var dbSize string if dbFile, err := os.Stat(util.DBPath); nil == err { dbSize = humanize.BytesCustomCeil(uint64(dbFile.Size()), 2) @@ -982,7 +960,8 @@ func clearWorkspaceTemp() { os.RemoveAll(filepath.Join(util.TempDir, "import")) os.RemoveAll(filepath.Join(util.TempDir, "repo")) os.RemoveAll(filepath.Join(util.TempDir, "os")) - os.RemoveAll(filepath.Join(util.TempDir, "blocktree.msgpack")) // v2.7.2 前旧版的块数数据 + os.RemoveAll(filepath.Join(util.TempDir, "blocktree.msgpack")) // v2.7.2 前旧版的块树数据 + os.RemoveAll(filepath.Join(util.TempDir, "blocktree")) // v3.1.0 前旧版的块树数据 // 退出时自动删除超过 7 天的安装包 https://github.com/siyuan-note/siyuan/issues/6128 install := filepath.Join(util.TempDir, "install") diff --git a/kernel/model/file.go b/kernel/model/file.go index e4e3fc4e374..c09571b5b10 100644 --- a/kernel/model/file.go +++ b/kernel/model/file.go @@ -1086,7 +1086,7 @@ func indexWriteTreeIndexQueue(tree *parse.Tree) (err error) { } func indexWriteTreeUpsertQueue(tree *parse.Tree) (err error) { - treenode.IndexBlockTree(tree) + treenode.UpsertBlockTree(tree) return writeTreeUpsertQueue(tree) } @@ -1095,7 +1095,7 @@ func renameWriteJSONQueue(tree *parse.Tree) (err error) { return } sql.RenameTreeQueue(tree) - treenode.IndexBlockTree(tree) + treenode.UpsertBlockTree(tree) return } diff --git a/kernel/model/index_fix.go b/kernel/model/index_fix.go index 819024a5ab4..fe6ff34d546 100644 --- a/kernel/model/index_fix.go +++ b/kernel/model/index_fix.go @@ -478,7 +478,7 @@ func reindexTree0(tree *parse.Tree, i, size int) { tree.Root.SetIALAttr("updated", updated) indexWriteTreeUpsertQueue(tree) } else { - treenode.IndexBlockTree(tree) + treenode.UpsertBlockTree(tree) sql.IndexTreeQueue(tree) } diff --git a/kernel/model/mount.go b/kernel/model/mount.go index 92768ca189c..1ec00f1784b 100644 --- a/kernel/model/mount.go +++ b/kernel/model/mount.go @@ -30,7 +30,6 @@ import ( "github.com/88250/lute/ast" "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" - "github.com/siyuan-note/siyuan/kernel/treenode" "github.com/siyuan-note/siyuan/kernel/util" ) @@ -247,7 +246,6 @@ func Mount(boxID string) (alreadyMount bool, err error) { box.Index() // 缓存根一级的文档树展开 ListDocTree(box.ID, "/", util.SortModeUnassigned, false, false, Conf.FileTree.MaxListCount) - treenode.SaveBlockTree(false) util.ClearPushProgress(100) if reMountGuide { diff --git a/kernel/model/sync.go b/kernel/model/sync.go index c39608ca2ed..cc39027879d 100644 --- a/kernel/model/sync.go +++ b/kernel/model/sync.go @@ -352,7 +352,7 @@ func upsertIndexes(upsertFilePaths []string) (upsertRootIDs []string) { if nil != err0 { continue } - treenode.IndexBlockTree(tree) + treenode.UpsertBlockTree(tree) sql.UpsertTreeQueue(tree) bts := treenode.GetBlockTreesByRootID(tree.ID) diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index 631fa77c890..0504f3ce131 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -1378,7 +1378,7 @@ func (tx *Transaction) loadTree(id string) (ret *parse.Tree, err error) { func (tx *Transaction) writeTree(tree *parse.Tree) (err error) { tx.trees[tree.ID] = tree - treenode.IndexBlockTree(tree) + treenode.UpsertBlockTree(tree) return } diff --git a/kernel/model/tree.go b/kernel/model/tree.go index 7fef7c18673..988af45361a 100644 --- a/kernel/model/tree.go +++ b/kernel/model/tree.go @@ -274,7 +274,7 @@ func searchTreeInFilesystem(rootID string) { return } - treenode.IndexBlockTree(tree) + treenode.UpsertBlockTree(tree) sql.IndexTreeQueue(tree) logging.LogInfof("reindexed tree by filesystem [rootID=%s]", rootID) } diff --git a/kernel/sql/database.go b/kernel/sql/database.go index ff51c287f90..fafbf320031 100644 --- a/kernel/sql/database.go +++ b/kernel/sql/database.go @@ -82,6 +82,7 @@ func InitDatabase(forceRebuild bool) (err error) { } initDBConnection() + treenode.InitBlockTree(forceRebuild) if !forceRebuild { // 检查数据库结构版本,如果版本不一致的话说明改过表结构,需要重建 @@ -101,9 +102,6 @@ func InitDatabase(forceRebuild bool) (err error) { err = nil } } - if gulu.File.IsExist(util.BlockTreePath) { - treenode.InitBlockTree(true) - } initDBConnection() initDBTables() @@ -1278,6 +1276,11 @@ func CloseDatabase() { logging.LogErrorf("close history database failed: %s", err) return } + if err := assetContentDB.Close(); nil != err { + logging.LogErrorf("close asset content database failed: %s", err) + return + } + treenode.CloseDatabase() logging.LogInfof("closed database") } diff --git a/kernel/treenode/blocktree.go b/kernel/treenode/blocktree.go index 8910cd1fcb6..8f6064395c8 100644 --- a/kernel/treenode/blocktree.go +++ b/kernel/treenode/blocktree.go @@ -17,33 +17,22 @@ package treenode import ( + "bytes" + "database/sql" + "errors" "os" - "path/filepath" - "strings" + "runtime" + "runtime/debug" "sync" - "sync/atomic" "time" - "github.com/88250/go-humanize" "github.com/88250/gulu" "github.com/88250/lute/ast" "github.com/88250/lute/parse" - "github.com/panjf2000/ants/v2" - util2 "github.com/siyuan-note/dejavu/util" "github.com/siyuan-note/logging" - "github.com/siyuan-note/siyuan/kernel/task" "github.com/siyuan-note/siyuan/kernel/util" - "github.com/vmihailenco/msgpack/v5" ) -var blockTrees = &sync.Map{} - -type btSlice struct { - data map[string]*BlockTree - changed time.Time - m *sync.Mutex -} - type BlockTree struct { ID string // 块 ID RootID string // 根 ID @@ -55,131 +44,237 @@ type BlockTree struct { Type string // 类型 } +var ( + db *sql.DB +) + +func initDatabase(forceRebuild bool) (err error) { + initDBConnection() + + if !forceRebuild { + if !gulu.File.IsExist(util.BlockTreeDBPath) { + forceRebuild = true + } + } + if !forceRebuild { + return + } + + closeDatabase() + if gulu.File.IsExist(util.BlockTreeDBPath) { + if err = removeDatabaseFile(); nil != err { + logging.LogErrorf("remove database file [%s] failed: %s", util.BlockTreeDBPath, err) + err = nil + } + } + + initDBConnection() + initDBTables() + + logging.LogInfof("reinitialized database [%s]", util.BlockTreeDBPath) + return +} + +func initDBTables() { + _, err := db.Exec("DROP TABLE IF EXISTS blocktrees") + if nil != err { + logging.LogFatalf(logging.ExitCodeReadOnlyDatabase, "drop table [blocks] failed: %s", err) + } + _, err = db.Exec("CREATE TABLE blocktrees (id, root_id, parent_id, box_id, path, hpath, updated, type)") + if nil != err { + logging.LogFatalf(logging.ExitCodeReadOnlyDatabase, "create table [blocktrees] failed: %s", err) + } +} + +func initDBConnection() { + if nil != db { + closeDatabase() + } + dsn := util.BlockTreeDBPath + "?_journal_mode=WAL" + + "&_synchronous=OFF" + + "&_mmap_size=2684354560" + + "&_secure_delete=OFF" + + "&_cache_size=-20480" + + "&_page_size=32768" + + "&_busy_timeout=7000" + + "&_ignore_check_constraints=ON" + + "&_temp_store=MEMORY" + + "&_case_sensitive_like=OFF" + var err error + db, err = sql.Open("sqlite3_extended", dsn) + if nil != err { + logging.LogFatalf(logging.ExitCodeReadOnlyDatabase, "create database failed: %s", err) + } + db.SetMaxIdleConns(7) + db.SetMaxOpenConns(7) + db.SetConnMaxLifetime(365 * 24 * time.Hour) +} + +func CloseDatabase() { + closeDatabase() +} + +func closeDatabase() { + if nil == db { + return + } + + if err := db.Close(); nil != err { + logging.LogErrorf("close database failed: %s", err) + } + debug.FreeOSMemory() + runtime.GC() // 没有这句的话文件句柄不会释放,后面就无法删除文件 + return +} + +func removeDatabaseFile() (err error) { + err = os.RemoveAll(util.BlockTreeDBPath) + if nil != err { + return + } + err = os.RemoveAll(util.BlockTreeDBPath + "-shm") + if nil != err { + return + } + err = os.RemoveAll(util.BlockTreeDBPath + "-wal") + if nil != err { + return + } + return +} + func GetBlockTreesByType(typ string) (ret []*BlockTree) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.Type == typ { - ret = append(ret, b) - } + sqlStmt := "SELECT * FROM blocktrees WHERE type = ?" + rows, err := db.Query(sqlStmt) + if nil != err { + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } + defer rows.Close() + for rows.Next() { + var block BlockTree + if err = rows.Scan(&block.ID, &block.RootID, &block.ParentID, &block.BoxID, &block.Path, &block.HPath, &block.Updated, &block.Type); nil != err { + logging.LogErrorf("query scan field failed: %s", err) + return } - slice.m.Unlock() - return true - }) + ret = append(ret, &block) + } return } func GetBlockTreeByPath(path string) (ret *BlockTree) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.Path == path { - ret = b - break - } + ret = &BlockTree{} + sqlStmt := "SELECT * FROM blocktrees WHERE path = ?" + err := db.QueryRow(sqlStmt, path).Scan(&ret.ID, &ret.RootID, &ret.ParentID, &ret.BoxID, &ret.Path, &ret.HPath, &ret.Updated, &ret.Type) + if nil != err { + ret = nil + if errors.Is(err, sql.ErrNoRows) { + return } - slice.m.Unlock() - return nil == ret - }) + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } return } func CountTrees() (ret int) { - roots := map[string]bool{} - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - roots[b.RootID] = true + sqlStmt := "SELECT COUNT(*) FROM blocktrees WHERE type = 'd'" + err := db.QueryRow(sqlStmt).Scan(&ret) + if nil != err { + if errors.Is(err, sql.ErrNoRows) { + return 0 } - slice.m.Unlock() - return true - }) - ret = len(roots) + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + } return } func CountBlocks() (ret int) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - ret += len(slice.data) - slice.m.Unlock() - return true - }) + sqlStmt := "SELECT COUNT(*) FROM blocktrees" + err := db.QueryRow(sqlStmt).Scan(&ret) + if nil != err { + if errors.Is(err, sql.ErrNoRows) { + return 0 + } + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + } return } func GetBlockTreeRootByPath(boxID, path string) (ret *BlockTree) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.BoxID == boxID && b.Path == path && b.RootID == b.ID { - ret = b - break - } + ret = &BlockTree{} + sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND path = ?" + err := db.QueryRow(sqlStmt, boxID, path).Scan(&ret.ID, &ret.RootID, &ret.ParentID, &ret.BoxID, &ret.Path, &ret.HPath, &ret.Updated, &ret.Type) + if nil != err { + ret = nil + if errors.Is(err, sql.ErrNoRows) { + return } - slice.m.Unlock() - return nil == ret - }) + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } return } func GetBlockTreeRootByHPath(boxID, hPath string) (ret *BlockTree) { + ret = &BlockTree{} hPath = gulu.Str.RemoveInvisible(hPath) - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.BoxID == boxID && b.HPath == hPath && b.RootID == b.ID { - ret = b - break - } + sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ?" + err := db.QueryRow(sqlStmt, boxID, hPath).Scan(&ret.ID, &ret.RootID, &ret.ParentID, &ret.BoxID, &ret.Path, &ret.HPath, &ret.Updated, &ret.Type) + if nil != err { + ret = nil + if errors.Is(err, sql.ErrNoRows) { + return } - slice.m.Unlock() - return nil == ret - }) + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } return } func GetBlockTreeRootsByHPath(boxID, hPath string) (ret []*BlockTree) { hPath = gulu.Str.RemoveInvisible(hPath) - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.BoxID == boxID && b.HPath == hPath && b.RootID == b.ID { - ret = append(ret, b) - } + sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ?" + rows, err := db.Query(sqlStmt, boxID, hPath) + if nil != err { + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } + defer rows.Close() + for rows.Next() { + var block BlockTree + if err = rows.Scan(&block.ID, &block.RootID, &block.ParentID, &block.BoxID, &block.Path, &block.HPath, &block.Updated, &block.Type); nil != err { + logging.LogErrorf("query scan field failed: %s", err) + return } - slice.m.Unlock() - return true - }) + ret = append(ret, &block) + } return } func GetBlockTreeRootByHPathPreferredParentID(boxID, hPath, preferredParentID string) (ret *BlockTree) { hPath = gulu.Str.RemoveInvisible(hPath) var roots []*BlockTree - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.BoxID == boxID && b.HPath == hPath && b.RootID == b.ID { - if "" == preferredParentID { - ret = b - break - } - - roots = append(roots, b) - } + sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ? AND parent_id = ?" + rows, err := db.Query(sqlStmt, boxID, hPath, preferredParentID) + if nil != err { + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } + defer rows.Close() + for rows.Next() { + var block BlockTree + if err = rows.Scan(&block.ID, &block.RootID, &block.ParentID, &block.BoxID, &block.Path, &block.HPath, &block.Updated, &block.Type); nil != err { + logging.LogErrorf("query scan field failed: %s", err) + return } - slice.m.Unlock() - return nil == ret - }) + if "" == preferredParentID { + ret = &block + return + } + roots = append(roots, &block) + } + if 1 > len(roots) { return } @@ -195,16 +290,17 @@ func GetBlockTreeRootByHPathPreferredParentID(boxID, hPath, preferredParentID st } func ExistBlockTree(id string) bool { - hash := btHash(id) - val, ok := blockTrees.Load(hash) - if !ok { + sqlStmt := "SELECT COUNT(*) FROM blocktrees WHERE id = ?" + var count int + err := db.QueryRow(sqlStmt, id).Scan(&count) + if nil != err { + if errors.Is(err, sql.ErrNoRows) { + return false + } + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) return false } - slice := val.(*btSlice) - slice.m.Lock() - _, ok = slice.data[id] - slice.m.Unlock() - return ok + return 0 < count } func GetBlockTree(id string) (ret *BlockTree) { @@ -212,15 +308,17 @@ func GetBlockTree(id string) (ret *BlockTree) { return } - hash := btHash(id) - val, ok := blockTrees.Load(hash) - if !ok { + ret = &BlockTree{} + sqlStmt := "SELECT * FROM blocktrees WHERE id = ?" + err := db.QueryRow(sqlStmt, id).Scan(&ret.ID, &ret.RootID, &ret.ParentID, &ret.BoxID, &ret.Path, &ret.HPath, &ret.Updated, &ret.Type) + if nil != err { + ret = nil + if errors.Is(err, sql.ErrNoRows) { + return + } + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, logging.ShortStack()) return } - slice := val.(*btSlice) - slice.m.Lock() - ret = slice.data[id] - slice.m.Unlock() return } @@ -230,170 +328,169 @@ func SetBlockTreePath(tree *parse.Tree) { } func RemoveBlockTreesByRootID(rootID string) { - var ids []string - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.RootID == rootID { - ids = append(ids, b.ID) - } - } - slice.m.Unlock() - return true - }) - - ids = gulu.Str.RemoveDuplicatedElem(ids) - for _, id := range ids { - val, ok := blockTrees.Load(btHash(id)) - if !ok { - continue - } - slice := val.(*btSlice) - slice.m.Lock() - delete(slice.data, id) - slice.changed = time.Now() - slice.m.Unlock() + sqlStmt := "DELETE FROM blocktrees WHERE root_id = ?" + _, err := db.Exec(sqlStmt, rootID) + if nil != err { + logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err) + return } } func GetBlockTreesByPathPrefix(pathPrefix string) (ret []*BlockTree) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if strings.HasPrefix(b.Path, pathPrefix) { - ret = append(ret, b) - } + sqlStmt := "SELECT * FROM blocktrees WHERE path LIKE ?" + rows, err := db.Query(sqlStmt, pathPrefix+"%") + if nil != err { + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } + defer rows.Close() + for rows.Next() { + var block BlockTree + if err = rows.Scan(&block.ID, &block.RootID, &block.ParentID, &block.BoxID, &block.Path, &block.HPath, &block.Updated, &block.Type); nil != err { + logging.LogErrorf("query scan field failed: %s", err) + return } - slice.m.Unlock() - return true - }) + ret = append(ret, &block) + } return } func GetBlockTreesByRootID(rootID string) (ret []*BlockTree) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.RootID == rootID { - ret = append(ret, b) - } + sqlStmt := "SELECT * FROM blocktrees WHERE root_id = ?" + rows, err := db.Query(sqlStmt, rootID) + if nil != err { + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } + defer rows.Close() + for rows.Next() { + var block BlockTree + if err = rows.Scan(&block.ID, &block.RootID, &block.ParentID, &block.BoxID, &block.Path, &block.HPath, &block.Updated, &block.Type); nil != err { + logging.LogErrorf("query scan field failed: %s", err) + return } - slice.m.Unlock() - return true - }) + ret = append(ret, &block) + } return } func RemoveBlockTreesByPathPrefix(pathPrefix string) { - var ids []string - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if strings.HasPrefix(b.Path, pathPrefix) { - ids = append(ids, b.ID) - } - } - slice.m.Unlock() - return true - }) - - ids = gulu.Str.RemoveDuplicatedElem(ids) - for _, id := range ids { - val, ok := blockTrees.Load(btHash(id)) - if !ok { - continue - } - slice := val.(*btSlice) - slice.m.Lock() - delete(slice.data, id) - slice.changed = time.Now() - slice.m.Unlock() + sqlStmt := "DELETE FROM blocktrees WHERE path LIKE ?" + _, err := db.Exec(sqlStmt, pathPrefix+"%") + if nil != err { + logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err) + return } } func GetBlockTreesByBoxID(boxID string) (ret []*BlockTree) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.BoxID == boxID { - ret = append(ret, b) - } + sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ?" + rows, err := db.Query(sqlStmt, boxID) + if nil != err { + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } + defer rows.Close() + for rows.Next() { + var block BlockTree + if err = rows.Scan(&block.ID, &block.RootID, &block.ParentID, &block.BoxID, &block.Path, &block.HPath, &block.Updated, &block.Type); nil != err { + logging.LogErrorf("query scan field failed: %s", err) + return } - slice.m.Unlock() - return true - }) + ret = append(ret, &block) + } return } func RemoveBlockTreesByBoxID(boxID string) (ids []string) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.BoxID == boxID { - ids = append(ids, b.ID) - } + sqlStmt := "SELECT id FROM blocktrees WHERE box_id = ?" + rows, err := db.Query(sqlStmt, boxID) + if nil != err { + logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err) + return + } + defer rows.Close() + for rows.Next() { + var id string + if err = rows.Scan(&id); nil != err { + logging.LogErrorf("query scan field failed: %s", err) + return } - slice.m.Unlock() - return true - }) + ids = append(ids, id) + } - ids = gulu.Str.RemoveDuplicatedElem(ids) - for _, id := range ids { - val, ok := blockTrees.Load(btHash(id)) - if !ok { - continue - } - slice := val.(*btSlice) - slice.m.Lock() - delete(slice.data, id) - slice.changed = time.Now() - slice.m.Unlock() + sqlStmt = "DELETE FROM blocktrees WHERE box_id = ?" + _, err = db.Exec(sqlStmt, boxID) + if nil != err { + logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err) + return } return } func RemoveBlockTree(id string) { - val, ok := blockTrees.Load(btHash(id)) - if !ok { + sqlStmt := "DELETE FROM blocktrees WHERE id = ?" + _, err := db.Exec(sqlStmt, id) + if nil != err { + logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err) return } - slice := val.(*btSlice) - slice.m.Lock() - delete(slice.data, id) - slice.changed = time.Now() - slice.m.Unlock() } +var indexBlockTreeLock = sync.Mutex{} + func IndexBlockTree(tree *parse.Tree) { var changedNodes []*ast.Node ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { - if !entering || !n.IsBlock() { - return ast.WalkContinue - } - if "" == n.ID { + if !entering || !n.IsBlock() || "" == n.ID { return ast.WalkContinue } - hash := btHash(n.ID) - val, ok := blockTrees.Load(hash) - if !ok { - val = &btSlice{data: map[string]*BlockTree{}, changed: time.Time{}, m: &sync.Mutex{}} - blockTrees.Store(hash, val) + changedNodes = append(changedNodes, n) + return ast.WalkContinue + }) + + indexBlockTreeLock.Lock() + defer indexBlockTreeLock.Unlock() + + tx, err := db.Begin() + if nil != err { + logging.LogErrorf("begin transaction failed: %s", err) + return + } + + sqlStmt := "INSERT INTO blocktrees (id, root_id, parent_id, box_id, path, hpath, updated, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" + for _, n := range changedNodes { + var parentID string + if nil != n.Parent { + parentID = n.Parent.ID } - slice := val.(*btSlice) + if _, err = tx.Exec(sqlStmt, n.ID, tree.ID, parentID, tree.Box, tree.Path, tree.HPath, n.IALAttr("updated"), TypeAbbr(n.Type.String())); nil != err { + tx.Rollback() + logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err) + return + } + } + if err = tx.Commit(); nil != err { + logging.LogErrorf("commit transaction failed: %s", err) + } +} + +func UpsertBlockTree(tree *parse.Tree) { + oldBts := map[string]*BlockTree{} + bts := GetBlockTreesByRootID(tree.ID) + for _, bt := range bts { + oldBts[bt.ID] = bt + } - slice.m.Lock() - bt := slice.data[n.ID] - slice.m.Unlock() + var changedNodes []*ast.Node + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || !n.IsBlock() || "" == n.ID { + return ast.WalkContinue + } - if nil != bt { - if bt.Updated != n.IALAttr("updated") || bt.Type != TypeAbbr(n.Type.String()) || bt.Path != tree.Path || bt.BoxID != tree.Box || bt.HPath != tree.HPath { + if oldBt, found := oldBts[n.ID]; found { + if oldBt.Updated != n.IALAttr("updated") || oldBt.Type != TypeAbbr(n.Type.String()) || oldBt.Path != tree.Path || oldBt.BoxID != tree.Box || oldBt.HPath != tree.HPath { children := ChildBlockNodes(n) // 需要考虑子块,因为一些操作(比如移动块)后需要同时更新子块 changedNodes = append(changedNodes, children...) } @@ -404,177 +501,58 @@ func IndexBlockTree(tree *parse.Tree) { return ast.WalkContinue }) - for _, n := range changedNodes { - updateBtSlice(n, tree) - } -} - -func updateBtSlice(n *ast.Node, tree *parse.Tree) { - var parentID string - if nil != n.Parent { - parentID = n.Parent.ID + ids := bytes.Buffer{} + for i, n := range changedNodes { + ids.WriteString("'") + ids.WriteString(n.ID) + ids.WriteString("'") + if i < len(changedNodes)-1 { + ids.WriteString(",") + } } - hash := btHash(n.ID) - val, ok := blockTrees.Load(hash) - if !ok { - val = &btSlice{data: map[string]*BlockTree{}, changed: time.Time{}, m: &sync.Mutex{}} - blockTrees.Store(hash, val) - } - slice := val.(*btSlice) - slice.m.Lock() - slice.data[n.ID] = &BlockTree{ID: n.ID, ParentID: parentID, RootID: tree.ID, BoxID: tree.Box, Path: tree.Path, HPath: tree.HPath, Updated: n.IALAttr("updated"), Type: TypeAbbr(n.Type.String())} - slice.changed = time.Now() - slice.m.Unlock() -} + indexBlockTreeLock.Lock() + defer indexBlockTreeLock.Unlock() -var blockTreeLock = sync.Mutex{} - -func InitBlockTree(force bool) { - blockTreeLock.Lock() - defer blockTreeLock.Unlock() - - start := time.Now() - if force { - err := os.RemoveAll(util.BlockTreePath) - if nil != err { - logging.LogErrorf("remove block tree file failed: %s", err) - } - blockTrees = &sync.Map{} + tx, err := db.Begin() + if nil != err { + logging.LogErrorf("begin transaction failed: %s", err) return } - entries, err := os.ReadDir(util.BlockTreePath) + sqlStmt := "DELETE FROM blocktrees WHERE id IN (" + ids.String() + ")" + + _, err = tx.Exec(sqlStmt) if nil != err { - logging.LogErrorf("read block tree dir failed: %s", err) - os.Exit(logging.ExitCodeFileSysErr) + tx.Rollback() + logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err) return } - - loadErr := atomic.Bool{} - size := atomic.Int64{} - waitGroup := &sync.WaitGroup{} - p, _ := ants.NewPoolWithFunc(4, func(arg interface{}) { - defer waitGroup.Done() - - entry := arg.(os.DirEntry) - p := filepath.Join(util.BlockTreePath, entry.Name()) - - f, err := os.OpenFile(p, os.O_RDONLY, 0644) - if nil != err { - logging.LogErrorf("open block tree failed: %s", err) - loadErr.Store(true) - return - } - defer f.Close() - - info, err := f.Stat() - if nil != err { - logging.LogErrorf("stat block tree failed: %s", err) - loadErr.Store(true) - return + sqlStmt = "INSERT INTO blocktrees (id, root_id, parent_id, box_id, path, hpath, updated, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" + for _, n := range changedNodes { + var parentID string + if nil != n.Parent { + parentID = n.Parent.ID } - size.Add(info.Size()) - - sliceData := map[string]*BlockTree{} - if err = msgpack.NewDecoder(f).Decode(&sliceData); nil != err { - logging.LogErrorf("unmarshal block tree failed: %s", err) - loadErr.Store(true) + if _, err = tx.Exec(sqlStmt, n.ID, tree.ID, parentID, tree.Box, tree.Path, tree.HPath, n.IALAttr("updated"), TypeAbbr(n.Type.String())); nil != err { + tx.Rollback() + logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err) return } - - name := entry.Name()[0:strings.Index(entry.Name(), ".")] - blockTrees.Store(name, &btSlice{data: sliceData, changed: time.Time{}, m: &sync.Mutex{}}) - }) - for _, entry := range entries { - if !strings.HasSuffix(entry.Name(), ".msgpack") { - continue - } - - waitGroup.Add(1) - p.Invoke(entry) } - - waitGroup.Wait() - p.Release() - - if loadErr.Load() { - logging.LogInfof("cause block tree load error, remove block tree file") - if removeErr := os.RemoveAll(util.BlockTreePath); nil != removeErr { - logging.LogErrorf("remove block tree file failed: %s", removeErr) - os.Exit(logging.ExitCodeFileSysErr) - return - } - blockTrees = &sync.Map{} - return + if err = tx.Commit(); nil != err { + logging.LogErrorf("commit transaction failed: %s", err) } - - elapsed := time.Since(start).Seconds() - logging.LogInfof("read block tree [%s] to [%s], elapsed [%.2fs]", humanize.BytesCustomCeil(uint64(size.Load()), 2), util.BlockTreePath, elapsed) - return } -func SaveBlockTreeJob() { - SaveBlockTree(false) -} - -func SaveBlockTree(force bool) { - blockTreeLock.Lock() - defer blockTreeLock.Unlock() - - if task.ContainIndexTask() { - //logging.LogInfof("skip saving block tree because indexing") - return - } - //logging.LogInfof("saving block tree") - - start := time.Now() - if err := os.MkdirAll(util.BlockTreePath, 0755); nil != err { - logging.LogErrorf("create block tree dir [%s] failed: %s", util.BlockTreePath, err) - os.Exit(logging.ExitCodeFileSysErr) +func InitBlockTree(force bool) { + err := initDatabase(force) + if nil != err { + logging.LogErrorf("init database failed: %s", err) + os.Exit(logging.ExitCodeReadOnlyDatabase) return } - - size := uint64(0) - var count int - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - if !force && slice.changed.IsZero() { - slice.m.Unlock() - return true - } - - data, err := msgpack.Marshal(slice.data) - if nil != err { - logging.LogErrorf("marshal block tree failed: %s", err) - os.Exit(logging.ExitCodeFileSysErr) - return false - } - slice.m.Unlock() - - p := filepath.Join(util.BlockTreePath, key.(string)) + ".msgpack" - if err = gulu.File.WriteFileSafer(p, data, 0644); nil != err { - logging.LogErrorf("write block tree failed: %s", err) - os.Exit(logging.ExitCodeFileSysErr) - return false - } - - slice.m.Lock() - slice.changed = time.Time{} - slice.m.Unlock() - size += uint64(len(data)) - count++ - return true - }) - if 0 < count { - //logging.LogInfof("wrote block trees [%d]", count) - } - - elapsed := time.Since(start).Seconds() - if 2 < elapsed { - logging.LogWarnf("save block tree [size=%s] to [%s], elapsed [%.2fs]", humanize.BytesCustomCeil(size, 2), util.BlockTreePath, elapsed) - } + return } func CeilTreeCount(count int) int { @@ -602,7 +580,3 @@ func CeilBlockCount(count int) int { } return 10000*100 + 1 } - -func btHash(id string) string { - return util2.Hash([]byte(id))[0:2] -} diff --git a/kernel/treenode/blocktree_fix.go b/kernel/treenode/blocktree_fix.go index 510cef41f65..f9a5eabcc0a 100644 --- a/kernel/treenode/blocktree_fix.go +++ b/kernel/treenode/blocktree_fix.go @@ -18,7 +18,7 @@ package treenode import ( "github.com/88250/gulu" - "time" + "github.com/siyuan-note/logging" ) func ClearRedundantBlockTrees(boxID string, paths []string) { @@ -35,17 +35,21 @@ func getRedundantPaths(boxID string, paths []string) (ret []string) { } btPathsMap := map[string]bool{} - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.BoxID == boxID { - btPathsMap[b.Path] = true - } + sqlStmt := "SELECT path FROM blocktrees WHERE box_id = ?" + rows, err := db.Query(sqlStmt, boxID) + if nil != err { + logging.LogErrorf("query block tree failed: %s", err) + return + } + defer rows.Close() + for rows.Next() { + var path string + if err = rows.Scan(&path); nil != err { + logging.LogErrorf("scan block tree failed: %s", err) + return } - slice.m.Unlock() - return true - }) + btPathsMap[path] = true + } for p, _ := range btPathsMap { if !pathsMap[p] { @@ -57,18 +61,11 @@ func getRedundantPaths(boxID string, paths []string) (ret []string) { } func removeBlockTreesByPath(boxID, path string) { - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.Path == path && b.BoxID == boxID { - delete(slice.data, b.ID) - slice.changed = time.Now() - } - } - slice.m.Unlock() - return true - }) + sqlStmt := "DELETE FROM blocktrees WHERE box_id = ? AND path = ?" + _, err := db.Exec(sqlStmt, boxID, path) + if nil != err { + logging.LogErrorf("delete block tree failed: %s", err) + } } func GetNotExistPaths(boxID string, paths []string) (ret []string) { @@ -78,17 +75,21 @@ func GetNotExistPaths(boxID string, paths []string) (ret []string) { } btPathsMap := map[string]bool{} - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.BoxID == boxID { - btPathsMap[b.Path] = true - } + sqlStmt := "SELECT path FROM blocktrees WHERE box_id = ?" + rows, err := db.Query(sqlStmt, boxID) + if nil != err { + logging.LogErrorf("query block tree failed: %s", err) + return + } + defer rows.Close() + for rows.Next() { + var path string + if err = rows.Scan(&path); nil != err { + logging.LogErrorf("scan block tree failed: %s", err) + return } - slice.m.Unlock() - return true - }) + btPathsMap[path] = true + } for p, _ := range pathsMap { if !btPathsMap[p] { @@ -101,16 +102,20 @@ func GetNotExistPaths(boxID string, paths []string) (ret []string) { func GetRootUpdated() (ret map[string]string) { ret = map[string]string{} - blockTrees.Range(func(key, value interface{}) bool { - slice := value.(*btSlice) - slice.m.Lock() - for _, b := range slice.data { - if b.RootID == b.ID { - ret[b.RootID] = b.Updated - } + sqlStmt := "SELECT root_id, updated FROM blocktrees WHERE root_id = id AND type = 'd'" + rows, err := db.Query(sqlStmt) + if nil != err { + logging.LogErrorf("query block tree failed: %s", err) + return + } + defer rows.Close() + for rows.Next() { + var rootID, updated string + if err = rows.Scan(&rootID, &updated); nil != err { + logging.LogErrorf("scan block tree failed: %s", err) + return } - slice.m.Unlock() - return true - }) + ret[rootID] = updated + } return } diff --git a/kernel/util/working.go b/kernel/util/working.go index 54f15a0b47b..7bb51a468e6 100644 --- a/kernel/util/working.go +++ b/kernel/util/working.go @@ -209,7 +209,7 @@ var ( DBPath string // SQLite 数据库文件路径 HistoryDBPath string // SQLite 历史数据库文件路径 AssetContentDBPath string // SQLite 资源文件内容数据库文件路径 - BlockTreePath string // 区块树文件路径 + BlockTreeDBPath string // 区块树数据库文件路径 AppearancePath string // 配置目录下的外观目录 appearance/ 路径 ThemesPath string // 配置目录下的外观目录下的 themes/ 路径 IconsPath string // 配置目录下的外观目录下的 icons/ 路径 @@ -287,7 +287,7 @@ func initWorkspaceDir(workspaceArg string) { DBPath = filepath.Join(TempDir, DBName) HistoryDBPath = filepath.Join(TempDir, "history.db") AssetContentDBPath = filepath.Join(TempDir, "asset_content.db") - BlockTreePath = filepath.Join(TempDir, "blocktree") + BlockTreeDBPath = filepath.Join(TempDir, "blocktree.db") SnippetsPath = filepath.Join(DataDir, "snippets") } diff --git a/kernel/util/working_mobile.go b/kernel/util/working_mobile.go index b9bbd1ed4f3..85fc5cbafa9 100644 --- a/kernel/util/working_mobile.go +++ b/kernel/util/working_mobile.go @@ -159,7 +159,7 @@ func initWorkspaceDirMobile(workspaceBaseDir string) { DBPath = filepath.Join(TempDir, DBName) HistoryDBPath = filepath.Join(TempDir, "history.db") AssetContentDBPath = filepath.Join(TempDir, "asset_content.db") - BlockTreePath = filepath.Join(TempDir, "blocktree") + BlockTreeDBPath = filepath.Join(TempDir, "blocktree.db") SnippetsPath = filepath.Join(DataDir, "snippets") AppearancePath = filepath.Join(ConfDir, "appearance")