This commit is contained in:
Daniel 2024-06-20 22:53:27 +08:00
parent 32413fa11c
commit 7a3d4a05ad
No known key found for this signature in database
GPG Key ID: 86211BA83DF03017
22 changed files with 453 additions and 497 deletions

View File

@ -1377,7 +1377,7 @@
"88": "Finished parsing [%d] data files, remaining to be processed [%d]", "88": "Finished parsing [%d] data files, remaining to be processed [%d]",
"89": "[%d/%d] Created [%d] of data indexes of block-level elements [%s]", "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]", "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]", "92": "Parsing document tree [%s]",
"93": "[%d/%d] Cleaned up the index related to document [%s]", "93": "[%d/%d] Cleaned up the index related to document [%s]",
"94": "Upload failed: %s", "94": "Upload failed: %s",

View File

@ -1377,7 +1377,7 @@
"88": "Se ha terminado de analizar [%d] archivos de datos, quedan por procesar [%d]", "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]", "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]", "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]", "92": "Analizando el árbol del documento [%s]",
"93": "[%d/%d] ha limpiado el índice relacionado con el documento [%s]", "93": "[%d/%d] ha limpiado el índice relacionado con el documento [%s]",
"94": "Carga fallida: %s", "94": "Carga fallida: %s",

View File

@ -1377,7 +1377,7 @@
"88": "Fin de l'analyse des fichiers de données [%d], restant à traiter [%d]", "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]", "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]", "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]", "92": "Analyse de l'arborescence du document [%s]",
"93": "[%d/%d] a nettoyé l'index lié au document [%s]", "93": "[%d/%d] a nettoyé l'index lié au document [%s]",
"94": "Échec du téléchargement : %s", "94": "Échec du téléchargement : %s",

View File

@ -1377,7 +1377,7 @@
"88": "[%d] 個のデータファイルの解析が完了し、処理待ちのデータファイルが [%d] 個残っています", "88": "[%d] 個のデータファイルの解析が完了し、処理待ちのデータファイルが [%d] 個残っています",
"89": "[%d/%d] ブロックレベル要素 [%s] のデータインデックスを [%d] 個作成しました", "89": "[%d/%d] ブロックレベル要素 [%s] のデータインデックスを [%d] 個作成しました",
"90": "[%d/%d] ブロックレベル要素 [%s] の検索インデックスを [%d] 個作成しました", "90": "[%d/%d] ブロックレベル要素 [%s] の検索インデックスを [%d] 個作成しました",
"91": "ブロックツリーデータを読み込んでいます...", "91": "TODO",
"92": "ドキュメントツリーを解析しています [%s]", "92": "ドキュメントツリーを解析しています [%s]",
"93": "[%d/%d] ドキュメント [%s] に関連するインデックスをクリーンアップしました", "93": "[%d/%d] ドキュメント [%s] に関連するインデックスをクリーンアップしました",
"94": "アップロードに失敗しました: %s", "94": "アップロードに失敗しました: %s",

View File

@ -1377,7 +1377,7 @@
"88": "已完成解析 [%d] 個資料文件,剩餘待處理 [%d]", "88": "已完成解析 [%d] 個資料文件,剩餘待處理 [%d]",
"89": "[%d/%d] 已經創建 [%d] 個塊級元素的資料索引 [%s]", "89": "[%d/%d] 已經創建 [%d] 個塊級元素的資料索引 [%s]",
"90": "[%d/%d] 已經創建 [%d] 個塊級元素的搜索索引 [%s]", "90": "[%d/%d] 已經創建 [%d] 個塊級元素的搜索索引 [%s]",
"91": "正在讀取塊樹資料...", "91": "TODO",
"92": "正在解析文檔樹 [%s]", "92": "正在解析文檔樹 [%s]",
"93": "[%d/%d] 已經清理文檔 [%s] 相關的索引", "93": "[%d/%d] 已經清理文檔 [%s] 相關的索引",
"94": "上傳失敗:%s", "94": "上傳失敗:%s",

View File

@ -1377,7 +1377,7 @@
"88": "已完成解析 [%d] 个数据文件,剩余待处理 [%d]", "88": "已完成解析 [%d] 个数据文件,剩余待处理 [%d]",
"89": "[%d/%d] 已经创建 [%d] 个块级元素的数据索引 [%s]", "89": "[%d/%d] 已经创建 [%d] 个块级元素的数据索引 [%s]",
"90": "[%d/%d] 已经创建 [%d] 个块级元素的搜索索引 [%s]", "90": "[%d/%d] 已经创建 [%d] 个块级元素的搜索索引 [%s]",
"91": "正在读取块树数据...", "91": "TODO",
"92": "正在解析文档树 [%s]", "92": "正在解析文档树 [%s]",
"93": "[%d/%d] 已经清理文档 [%s] 相关的索引", "93": "[%d/%d] 已经清理文档 [%s] 相关的索引",
"94": "上传失败:%s", "94": "上传失败:%s",

View File

@ -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) logging.LogErrorf("rebuild parent tree [%s] failed: %s", parentAbsPath, writeErr)
} else { } else {
logging.LogInfof("rebuilt parent tree [%s]", parentAbsPath) logging.LogInfof("rebuilt parent tree [%s]", parentAbsPath)
treenode.IndexBlockTree(parentTree) treenode.UpsertBlockTree(parentTree)
} }
} else { } else {
logging.LogWarnf("read parent tree data [%s] failed: %s", parentAbsPath, readErr) 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() newP := treenode.NewParagraph()
tree.Root.AppendChild(newP) tree.Root.AppendChild(newP)
tree.Root.SetIALAttr("updated", util.TimeFromID(newP.ID)) tree.Root.SetIALAttr("updated", util.TimeFromID(newP.ID))
treenode.IndexBlockTree(tree) treenode.UpsertBlockTree(tree)
} }
filePath = filepath.Join(util.DataDir, tree.Box, tree.Path) filePath = filepath.Join(util.DataDir, tree.Box, tree.Path)

View File

@ -23,14 +23,12 @@ import (
"github.com/siyuan-note/siyuan/kernel/model" "github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/sql" "github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/task" "github.com/siyuan-note/siyuan/kernel/task"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util" "github.com/siyuan-note/siyuan/kernel/util"
) )
func StartCron() { func StartCron() {
go every(100*time.Millisecond, task.ExecTaskJob) go every(100*time.Millisecond, task.ExecTaskJob)
go every(5*time.Second, task.StatusJob) go every(5*time.Second, task.StatusJob)
go every(5*time.Second, treenode.SaveBlockTreeJob)
go every(5*time.Second, model.SyncDataJob) go every(5*time.Second, model.SyncDataJob)
go every(2*time.Hour, model.StatJob) go every(2*time.Hour, model.StatJob)
go every(2*time.Hour, model.RefreshCheckJob) go every(2*time.Hour, model.RefreshCheckJob)

View File

@ -825,7 +825,7 @@ func RenameAsset(oldPath, newName string) (err error) {
continue continue
} }
treenode.IndexBlockTree(tree) treenode.UpsertBlockTree(tree)
sql.UpsertTreeQueue(tree) sql.UpsertTreeQueue(tree)
util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title"))))

View File

@ -531,7 +531,6 @@ func fullReindex() {
for _, openedBox := range openedBoxes { for _, openedBox := range openedBoxes {
index(openedBox.ID) index(openedBox.ID)
} }
treenode.SaveBlockTree(true)
LoadFlashcards() LoadFlashcards()
debug.FreeOSMemory() debug.FreeOSMemory()
} }

View File

@ -627,7 +627,6 @@ func Close(force, setCurrentWorkspace bool, execInstallPkg int) (exitCode int) {
Conf.Close() Conf.Close()
sql.CloseDatabase() sql.CloseDatabase()
treenode.SaveBlockTree(false)
util.SaveAssetsTexts() util.SaveAssetsTexts()
clearWorkspaceTemp() clearWorkspaceTemp()
clearCorruptedNotebooks() clearCorruptedNotebooks()
@ -818,24 +817,7 @@ func (conf *AppConf) language(num int) (ret string) {
} }
func InitBoxes() { func InitBoxes() {
initialized := false initialized := 0 < treenode.CountBlocks()
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
}
for _, box := range Conf.GetOpenedBoxes() { for _, box := range Conf.GetOpenedBoxes() {
box.UpdateHistoryGenerated() // 初始化历史生成时间为当前时间 box.UpdateHistoryGenerated() // 初始化历史生成时间为当前时间
@ -844,10 +826,6 @@ func InitBoxes() {
} }
} }
if !initialized {
treenode.SaveBlockTree(true)
}
var dbSize string var dbSize string
if dbFile, err := os.Stat(util.DBPath); nil == err { if dbFile, err := os.Stat(util.DBPath); nil == err {
dbSize = humanize.BytesCustomCeil(uint64(dbFile.Size()), 2) 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, "import"))
os.RemoveAll(filepath.Join(util.TempDir, "repo")) os.RemoveAll(filepath.Join(util.TempDir, "repo"))
os.RemoveAll(filepath.Join(util.TempDir, "os")) 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 // 退出时自动删除超过 7 天的安装包 https://github.com/siyuan-note/siyuan/issues/6128
install := filepath.Join(util.TempDir, "install") install := filepath.Join(util.TempDir, "install")

View File

@ -1086,7 +1086,7 @@ func indexWriteTreeIndexQueue(tree *parse.Tree) (err error) {
} }
func indexWriteTreeUpsertQueue(tree *parse.Tree) (err error) { func indexWriteTreeUpsertQueue(tree *parse.Tree) (err error) {
treenode.IndexBlockTree(tree) treenode.UpsertBlockTree(tree)
return writeTreeUpsertQueue(tree) return writeTreeUpsertQueue(tree)
} }
@ -1095,7 +1095,7 @@ func renameWriteJSONQueue(tree *parse.Tree) (err error) {
return return
} }
sql.RenameTreeQueue(tree) sql.RenameTreeQueue(tree)
treenode.IndexBlockTree(tree) treenode.UpsertBlockTree(tree)
return return
} }

View File

@ -478,7 +478,7 @@ func reindexTree0(tree *parse.Tree, i, size int) {
tree.Root.SetIALAttr("updated", updated) tree.Root.SetIALAttr("updated", updated)
indexWriteTreeUpsertQueue(tree) indexWriteTreeUpsertQueue(tree)
} else { } else {
treenode.IndexBlockTree(tree) treenode.UpsertBlockTree(tree)
sql.IndexTreeQueue(tree) sql.IndexTreeQueue(tree)
} }

View File

@ -30,7 +30,6 @@ import (
"github.com/88250/lute/ast" "github.com/88250/lute/ast"
"github.com/siyuan-note/filelock" "github.com/siyuan-note/filelock"
"github.com/siyuan-note/logging" "github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util" "github.com/siyuan-note/siyuan/kernel/util"
) )
@ -247,7 +246,6 @@ func Mount(boxID string) (alreadyMount bool, err error) {
box.Index() box.Index()
// 缓存根一级的文档树展开 // 缓存根一级的文档树展开
ListDocTree(box.ID, "/", util.SortModeUnassigned, false, false, Conf.FileTree.MaxListCount) ListDocTree(box.ID, "/", util.SortModeUnassigned, false, false, Conf.FileTree.MaxListCount)
treenode.SaveBlockTree(false)
util.ClearPushProgress(100) util.ClearPushProgress(100)
if reMountGuide { if reMountGuide {

View File

@ -352,7 +352,7 @@ func upsertIndexes(upsertFilePaths []string) (upsertRootIDs []string) {
if nil != err0 { if nil != err0 {
continue continue
} }
treenode.IndexBlockTree(tree) treenode.UpsertBlockTree(tree)
sql.UpsertTreeQueue(tree) sql.UpsertTreeQueue(tree)
bts := treenode.GetBlockTreesByRootID(tree.ID) bts := treenode.GetBlockTreesByRootID(tree.ID)

View File

@ -1378,7 +1378,7 @@ func (tx *Transaction) loadTree(id string) (ret *parse.Tree, err error) {
func (tx *Transaction) writeTree(tree *parse.Tree) (err error) { func (tx *Transaction) writeTree(tree *parse.Tree) (err error) {
tx.trees[tree.ID] = tree tx.trees[tree.ID] = tree
treenode.IndexBlockTree(tree) treenode.UpsertBlockTree(tree)
return return
} }

View File

@ -274,7 +274,7 @@ func searchTreeInFilesystem(rootID string) {
return return
} }
treenode.IndexBlockTree(tree) treenode.UpsertBlockTree(tree)
sql.IndexTreeQueue(tree) sql.IndexTreeQueue(tree)
logging.LogInfof("reindexed tree by filesystem [rootID=%s]", rootID) logging.LogInfof("reindexed tree by filesystem [rootID=%s]", rootID)
} }

View File

@ -82,6 +82,7 @@ func InitDatabase(forceRebuild bool) (err error) {
} }
initDBConnection() initDBConnection()
treenode.InitBlockTree(forceRebuild)
if !forceRebuild { if !forceRebuild {
// 检查数据库结构版本,如果版本不一致的话说明改过表结构,需要重建 // 检查数据库结构版本,如果版本不一致的话说明改过表结构,需要重建
@ -101,9 +102,6 @@ func InitDatabase(forceRebuild bool) (err error) {
err = nil err = nil
} }
} }
if gulu.File.IsExist(util.BlockTreePath) {
treenode.InitBlockTree(true)
}
initDBConnection() initDBConnection()
initDBTables() initDBTables()
@ -1278,6 +1276,11 @@ func CloseDatabase() {
logging.LogErrorf("close history database failed: %s", err) logging.LogErrorf("close history database failed: %s", err)
return return
} }
if err := assetContentDB.Close(); nil != err {
logging.LogErrorf("close asset content database failed: %s", err)
return
}
treenode.CloseDatabase()
logging.LogInfof("closed database") logging.LogInfof("closed database")
} }

View File

@ -17,33 +17,22 @@
package treenode package treenode
import ( import (
"bytes"
"database/sql"
"errors"
"os" "os"
"path/filepath" "runtime"
"strings" "runtime/debug"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/88250/go-humanize"
"github.com/88250/gulu" "github.com/88250/gulu"
"github.com/88250/lute/ast" "github.com/88250/lute/ast"
"github.com/88250/lute/parse" "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/logging"
"github.com/siyuan-note/siyuan/kernel/task"
"github.com/siyuan-note/siyuan/kernel/util" "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 { type BlockTree struct {
ID string // 块 ID ID string // 块 ID
RootID string // 根 ID RootID string // 根 ID
@ -55,131 +44,237 @@ type BlockTree struct {
Type string // 类型 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) { func GetBlockTreesByType(typ string) (ret []*BlockTree) {
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT * FROM blocktrees WHERE type = ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
if b.Type == typ { return
ret = append(ret, b)
} }
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
}
ret = append(ret, &block)
} }
slice.m.Unlock()
return true
})
return return
} }
func GetBlockTreeByPath(path string) (ret *BlockTree) { func GetBlockTreeByPath(path string) (ret *BlockTree) {
blockTrees.Range(func(key, value interface{}) bool { ret = &BlockTree{}
slice := value.(*btSlice) sqlStmt := "SELECT * FROM blocktrees WHERE path = ?"
slice.m.Lock() err := db.QueryRow(sqlStmt, path).Scan(&ret.ID, &ret.RootID, &ret.ParentID, &ret.BoxID, &ret.Path, &ret.HPath, &ret.Updated, &ret.Type)
for _, b := range slice.data { if nil != err {
if b.Path == path { ret = nil
ret = b if errors.Is(err, sql.ErrNoRows) {
break return
} }
logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
return
} }
slice.m.Unlock()
return nil == ret
})
return return
} }
func CountTrees() (ret int) { func CountTrees() (ret int) {
roots := map[string]bool{} sqlStmt := "SELECT COUNT(*) FROM blocktrees WHERE type = 'd'"
blockTrees.Range(func(key, value interface{}) bool { err := db.QueryRow(sqlStmt).Scan(&ret)
slice := value.(*btSlice) if nil != err {
slice.m.Lock() if errors.Is(err, sql.ErrNoRows) {
for _, b := range slice.data { return 0
roots[b.RootID] = true }
logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
} }
slice.m.Unlock()
return true
})
ret = len(roots)
return return
} }
func CountBlocks() (ret int) { func CountBlocks() (ret int) {
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT COUNT(*) FROM blocktrees"
slice := value.(*btSlice) err := db.QueryRow(sqlStmt).Scan(&ret)
slice.m.Lock() if nil != err {
ret += len(slice.data) if errors.Is(err, sql.ErrNoRows) {
slice.m.Unlock() return 0
return true }
}) logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
}
return return
} }
func GetBlockTreeRootByPath(boxID, path string) (ret *BlockTree) { func GetBlockTreeRootByPath(boxID, path string) (ret *BlockTree) {
blockTrees.Range(func(key, value interface{}) bool { ret = &BlockTree{}
slice := value.(*btSlice) sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND path = ?"
slice.m.Lock() err := db.QueryRow(sqlStmt, boxID, path).Scan(&ret.ID, &ret.RootID, &ret.ParentID, &ret.BoxID, &ret.Path, &ret.HPath, &ret.Updated, &ret.Type)
for _, b := range slice.data { if nil != err {
if b.BoxID == boxID && b.Path == path && b.RootID == b.ID { ret = nil
ret = b if errors.Is(err, sql.ErrNoRows) {
break return
} }
logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
return
} }
slice.m.Unlock()
return nil == ret
})
return return
} }
func GetBlockTreeRootByHPath(boxID, hPath string) (ret *BlockTree) { func GetBlockTreeRootByHPath(boxID, hPath string) (ret *BlockTree) {
ret = &BlockTree{}
hPath = gulu.Str.RemoveInvisible(hPath) hPath = gulu.Str.RemoveInvisible(hPath)
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ?"
slice := value.(*btSlice) err := db.QueryRow(sqlStmt, boxID, hPath).Scan(&ret.ID, &ret.RootID, &ret.ParentID, &ret.BoxID, &ret.Path, &ret.HPath, &ret.Updated, &ret.Type)
slice.m.Lock() if nil != err {
for _, b := range slice.data { ret = nil
if b.BoxID == boxID && b.HPath == hPath && b.RootID == b.ID { if errors.Is(err, sql.ErrNoRows) {
ret = b return
break
} }
logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
return
} }
slice.m.Unlock()
return nil == ret
})
return return
} }
func GetBlockTreeRootsByHPath(boxID, hPath string) (ret []*BlockTree) { func GetBlockTreeRootsByHPath(boxID, hPath string) (ret []*BlockTree) {
hPath = gulu.Str.RemoveInvisible(hPath) hPath = gulu.Str.RemoveInvisible(hPath)
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt, boxID, hPath)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
if b.BoxID == boxID && b.HPath == hPath && b.RootID == b.ID { return
ret = append(ret, b)
} }
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
}
ret = append(ret, &block)
} }
slice.m.Unlock()
return true
})
return return
} }
func GetBlockTreeRootByHPathPreferredParentID(boxID, hPath, preferredParentID string) (ret *BlockTree) { func GetBlockTreeRootByHPathPreferredParentID(boxID, hPath, preferredParentID string) (ret *BlockTree) {
hPath = gulu.Str.RemoveInvisible(hPath) hPath = gulu.Str.RemoveInvisible(hPath)
var roots []*BlockTree var roots []*BlockTree
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ? AND parent_id = ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt, boxID, hPath, preferredParentID)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
if b.BoxID == boxID && b.HPath == hPath && b.RootID == b.ID { 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
}
if "" == preferredParentID { if "" == preferredParentID {
ret = b ret = &block
break return
}
roots = append(roots, &block)
} }
roots = append(roots, b)
}
}
slice.m.Unlock()
return nil == ret
})
if 1 > len(roots) { if 1 > len(roots) {
return return
} }
@ -195,16 +290,17 @@ func GetBlockTreeRootByHPathPreferredParentID(boxID, hPath, preferredParentID st
} }
func ExistBlockTree(id string) bool { func ExistBlockTree(id string) bool {
hash := btHash(id) sqlStmt := "SELECT COUNT(*) FROM blocktrees WHERE id = ?"
val, ok := blockTrees.Load(hash) var count int
if !ok { err := db.QueryRow(sqlStmt, id).Scan(&count)
if nil != err {
if errors.Is(err, sql.ErrNoRows) {
return false return false
} }
slice := val.(*btSlice) logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
slice.m.Lock() return false
_, ok = slice.data[id] }
slice.m.Unlock() return 0 < count
return ok
} }
func GetBlockTree(id string) (ret *BlockTree) { func GetBlockTree(id string) (ret *BlockTree) {
@ -212,15 +308,17 @@ func GetBlockTree(id string) (ret *BlockTree) {
return return
} }
hash := btHash(id) ret = &BlockTree{}
val, ok := blockTrees.Load(hash) sqlStmt := "SELECT * FROM blocktrees WHERE id = ?"
if !ok { 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 return
} }
slice := val.(*btSlice)
slice.m.Lock()
ret = slice.data[id]
slice.m.Unlock()
return return
} }
@ -230,170 +328,169 @@ func SetBlockTreePath(tree *parse.Tree) {
} }
func RemoveBlockTreesByRootID(rootID string) { func RemoveBlockTreesByRootID(rootID string) {
var ids []string sqlStmt := "DELETE FROM blocktrees WHERE root_id = ?"
blockTrees.Range(func(key, value interface{}) bool { _, err := db.Exec(sqlStmt, rootID)
slice := value.(*btSlice) if nil != err {
slice.m.Lock() logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err)
for _, b := range slice.data { return
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()
} }
} }
func GetBlockTreesByPathPrefix(pathPrefix string) (ret []*BlockTree) { func GetBlockTreesByPathPrefix(pathPrefix string) (ret []*BlockTree) {
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT * FROM blocktrees WHERE path LIKE ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt, pathPrefix+"%")
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
if strings.HasPrefix(b.Path, pathPrefix) { return
ret = append(ret, b)
} }
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
}
ret = append(ret, &block)
} }
slice.m.Unlock()
return true
})
return return
} }
func GetBlockTreesByRootID(rootID string) (ret []*BlockTree) { func GetBlockTreesByRootID(rootID string) (ret []*BlockTree) {
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT * FROM blocktrees WHERE root_id = ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt, rootID)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
if b.RootID == rootID { return
ret = append(ret, b)
} }
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
}
ret = append(ret, &block)
} }
slice.m.Unlock()
return true
})
return return
} }
func RemoveBlockTreesByPathPrefix(pathPrefix string) { func RemoveBlockTreesByPathPrefix(pathPrefix string) {
var ids []string sqlStmt := "DELETE FROM blocktrees WHERE path LIKE ?"
blockTrees.Range(func(key, value interface{}) bool { _, err := db.Exec(sqlStmt, pathPrefix+"%")
slice := value.(*btSlice) if nil != err {
slice.m.Lock() logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err)
for _, b := range slice.data { return
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()
} }
} }
func GetBlockTreesByBoxID(boxID string) (ret []*BlockTree) { func GetBlockTreesByBoxID(boxID string) (ret []*BlockTree) {
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt, boxID)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
if b.BoxID == boxID { return
ret = append(ret, b)
} }
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
}
ret = append(ret, &block)
} }
slice.m.Unlock()
return true
})
return return
} }
func RemoveBlockTreesByBoxID(boxID string) (ids []string) { func RemoveBlockTreesByBoxID(boxID string) (ids []string) {
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT id FROM blocktrees WHERE box_id = ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt, boxID)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("sql query [%s] failed: %s", sqlStmt, err)
if b.BoxID == boxID { return
ids = append(ids, b.ID)
} }
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
}
ids = append(ids, id)
} }
slice.m.Unlock()
return true
})
ids = gulu.Str.RemoveDuplicatedElem(ids) sqlStmt = "DELETE FROM blocktrees WHERE box_id = ?"
for _, id := range ids { _, err = db.Exec(sqlStmt, boxID)
val, ok := blockTrees.Load(btHash(id)) if nil != err {
if !ok { logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err)
continue return
}
slice := val.(*btSlice)
slice.m.Lock()
delete(slice.data, id)
slice.changed = time.Now()
slice.m.Unlock()
} }
return return
} }
func RemoveBlockTree(id string) { func RemoveBlockTree(id string) {
val, ok := blockTrees.Load(btHash(id)) sqlStmt := "DELETE FROM blocktrees WHERE id = ?"
if !ok { _, err := db.Exec(sqlStmt, id)
if nil != err {
logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err)
return 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) { func IndexBlockTree(tree *parse.Tree) {
var changedNodes []*ast.Node var changedNodes []*ast.Node
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering || !n.IsBlock() { if !entering || !n.IsBlock() || "" == n.ID {
return ast.WalkContinue
}
if "" == n.ID {
return ast.WalkContinue return ast.WalkContinue
} }
hash := btHash(n.ID) changedNodes = append(changedNodes, n)
val, ok := blockTrees.Load(hash) return ast.WalkContinue
if !ok { })
val = &btSlice{data: map[string]*BlockTree{}, changed: time.Time{}, m: &sync.Mutex{}}
blockTrees.Store(hash, val) indexBlockTreeLock.Lock()
defer indexBlockTreeLock.Unlock()
tx, err := db.Begin()
if nil != err {
logging.LogErrorf("begin transaction failed: %s", err)
return
} }
slice := val.(*btSlice)
slice.m.Lock() sqlStmt := "INSERT INTO blocktrees (id, root_id, parent_id, box_id, path, hpath, updated, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
bt := slice.data[n.ID] for _, n := range changedNodes {
slice.m.Unlock() var parentID string
if nil != n.Parent {
parentID = n.Parent.ID
}
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)
}
}
if nil != bt { func UpsertBlockTree(tree *parse.Tree) {
if bt.Updated != n.IALAttr("updated") || bt.Type != TypeAbbr(n.Type.String()) || bt.Path != tree.Path || bt.BoxID != tree.Box || bt.HPath != tree.HPath { oldBts := map[string]*BlockTree{}
bts := GetBlockTreesByRootID(tree.ID)
for _, bt := range bts {
oldBts[bt.ID] = bt
}
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 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) // 需要考虑子块,因为一些操作(比如移动块)后需要同时更新子块 children := ChildBlockNodes(n) // 需要考虑子块,因为一些操作(比如移动块)后需要同时更新子块
changedNodes = append(changedNodes, children...) changedNodes = append(changedNodes, children...)
} }
@ -404,177 +501,58 @@ func IndexBlockTree(tree *parse.Tree) {
return ast.WalkContinue return ast.WalkContinue
}) })
for _, n := range changedNodes { ids := bytes.Buffer{}
updateBtSlice(n, tree) for i, n := range changedNodes {
ids.WriteString("'")
ids.WriteString(n.ID)
ids.WriteString("'")
if i < len(changedNodes)-1 {
ids.WriteString(",")
}
} }
}
func updateBtSlice(n *ast.Node, tree *parse.Tree) { indexBlockTreeLock.Lock()
defer indexBlockTreeLock.Unlock()
tx, err := db.Begin()
if nil != err {
logging.LogErrorf("begin transaction failed: %s", err)
return
}
sqlStmt := "DELETE FROM blocktrees WHERE id IN (" + ids.String() + ")"
_, err = tx.Exec(sqlStmt)
if nil != err {
tx.Rollback()
logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, 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 var parentID string
if nil != n.Parent { if nil != n.Parent {
parentID = n.Parent.ID parentID = n.Parent.ID
} }
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 {
hash := btHash(n.ID) tx.Rollback()
val, ok := blockTrees.Load(hash) logging.LogErrorf("sql exec [%s] failed: %s", sqlStmt, err)
if !ok { return
val = &btSlice{data: map[string]*BlockTree{}, changed: time.Time{}, m: &sync.Mutex{}} }
blockTrees.Store(hash, val) }
if err = tx.Commit(); nil != err {
logging.LogErrorf("commit transaction failed: %s", err)
} }
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()
} }
var blockTreeLock = sync.Mutex{}
func InitBlockTree(force bool) { func InitBlockTree(force bool) {
blockTreeLock.Lock() err := initDatabase(force)
defer blockTreeLock.Unlock()
start := time.Now()
if force {
err := os.RemoveAll(util.BlockTreePath)
if nil != err { if nil != err {
logging.LogErrorf("remove block tree file failed: %s", err) logging.LogErrorf("init database failed: %s", err)
} os.Exit(logging.ExitCodeReadOnlyDatabase)
blockTrees = &sync.Map{}
return return
} }
entries, err := os.ReadDir(util.BlockTreePath)
if nil != err {
logging.LogErrorf("read block tree dir failed: %s", err)
os.Exit(logging.ExitCodeFileSysErr)
return 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
}
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)
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
}
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)
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)
}
} }
func CeilTreeCount(count int) int { func CeilTreeCount(count int) int {
@ -602,7 +580,3 @@ func CeilBlockCount(count int) int {
} }
return 10000*100 + 1 return 10000*100 + 1
} }
func btHash(id string) string {
return util2.Hash([]byte(id))[0:2]
}

View File

@ -18,7 +18,7 @@ package treenode
import ( import (
"github.com/88250/gulu" "github.com/88250/gulu"
"time" "github.com/siyuan-note/logging"
) )
func ClearRedundantBlockTrees(boxID string, paths []string) { func ClearRedundantBlockTrees(boxID string, paths []string) {
@ -35,17 +35,21 @@ func getRedundantPaths(boxID string, paths []string) (ret []string) {
} }
btPathsMap := map[string]bool{} btPathsMap := map[string]bool{}
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT path FROM blocktrees WHERE box_id = ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt, boxID)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("query block tree failed: %s", err)
if b.BoxID == boxID { return
btPathsMap[b.Path] = true
} }
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
}
btPathsMap[path] = true
} }
slice.m.Unlock()
return true
})
for p, _ := range btPathsMap { for p, _ := range btPathsMap {
if !pathsMap[p] { if !pathsMap[p] {
@ -57,18 +61,11 @@ func getRedundantPaths(boxID string, paths []string) (ret []string) {
} }
func removeBlockTreesByPath(boxID, path string) { func removeBlockTreesByPath(boxID, path string) {
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "DELETE FROM blocktrees WHERE box_id = ? AND path = ?"
slice := value.(*btSlice) _, err := db.Exec(sqlStmt, boxID, path)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("delete block tree failed: %s", err)
if b.Path == path && b.BoxID == boxID {
delete(slice.data, b.ID)
slice.changed = time.Now()
} }
}
slice.m.Unlock()
return true
})
} }
func GetNotExistPaths(boxID string, paths []string) (ret []string) { func GetNotExistPaths(boxID string, paths []string) (ret []string) {
@ -78,17 +75,21 @@ func GetNotExistPaths(boxID string, paths []string) (ret []string) {
} }
btPathsMap := map[string]bool{} btPathsMap := map[string]bool{}
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT path FROM blocktrees WHERE box_id = ?"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt, boxID)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("query block tree failed: %s", err)
if b.BoxID == boxID { return
btPathsMap[b.Path] = true
} }
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
}
btPathsMap[path] = true
} }
slice.m.Unlock()
return true
})
for p, _ := range pathsMap { for p, _ := range pathsMap {
if !btPathsMap[p] { if !btPathsMap[p] {
@ -101,16 +102,20 @@ func GetNotExistPaths(boxID string, paths []string) (ret []string) {
func GetRootUpdated() (ret map[string]string) { func GetRootUpdated() (ret map[string]string) {
ret = map[string]string{} ret = map[string]string{}
blockTrees.Range(func(key, value interface{}) bool { sqlStmt := "SELECT root_id, updated FROM blocktrees WHERE root_id = id AND type = 'd'"
slice := value.(*btSlice) rows, err := db.Query(sqlStmt)
slice.m.Lock() if nil != err {
for _, b := range slice.data { logging.LogErrorf("query block tree failed: %s", err)
if b.RootID == b.ID { return
ret[b.RootID] = b.Updated
} }
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
}
ret[rootID] = updated
} }
slice.m.Unlock()
return true
})
return return
} }

View File

@ -209,7 +209,7 @@ var (
DBPath string // SQLite 数据库文件路径 DBPath string // SQLite 数据库文件路径
HistoryDBPath string // SQLite 历史数据库文件路径 HistoryDBPath string // SQLite 历史数据库文件路径
AssetContentDBPath string // SQLite 资源文件内容数据库文件路径 AssetContentDBPath string // SQLite 资源文件内容数据库文件路径
BlockTreePath string // 区块树文件路径 BlockTreeDBPath string // 区块树数据库文件路径
AppearancePath string // 配置目录下的外观目录 appearance/ 路径 AppearancePath string // 配置目录下的外观目录 appearance/ 路径
ThemesPath string // 配置目录下的外观目录下的 themes/ 路径 ThemesPath string // 配置目录下的外观目录下的 themes/ 路径
IconsPath string // 配置目录下的外观目录下的 icons/ 路径 IconsPath string // 配置目录下的外观目录下的 icons/ 路径
@ -287,7 +287,7 @@ func initWorkspaceDir(workspaceArg string) {
DBPath = filepath.Join(TempDir, DBName) DBPath = filepath.Join(TempDir, DBName)
HistoryDBPath = filepath.Join(TempDir, "history.db") HistoryDBPath = filepath.Join(TempDir, "history.db")
AssetContentDBPath = filepath.Join(TempDir, "asset_content.db") AssetContentDBPath = filepath.Join(TempDir, "asset_content.db")
BlockTreePath = filepath.Join(TempDir, "blocktree") BlockTreeDBPath = filepath.Join(TempDir, "blocktree.db")
SnippetsPath = filepath.Join(DataDir, "snippets") SnippetsPath = filepath.Join(DataDir, "snippets")
} }

View File

@ -159,7 +159,7 @@ func initWorkspaceDirMobile(workspaceBaseDir string) {
DBPath = filepath.Join(TempDir, DBName) DBPath = filepath.Join(TempDir, DBName)
HistoryDBPath = filepath.Join(TempDir, "history.db") HistoryDBPath = filepath.Join(TempDir, "history.db")
AssetContentDBPath = filepath.Join(TempDir, "asset_content.db") AssetContentDBPath = filepath.Join(TempDir, "asset_content.db")
BlockTreePath = filepath.Join(TempDir, "blocktree") BlockTreeDBPath = filepath.Join(TempDir, "blocktree.db")
SnippetsPath = filepath.Join(DataDir, "snippets") SnippetsPath = filepath.Join(DataDir, "snippets")
AppearancePath = filepath.Join(ConfDir, "appearance") AppearancePath = filepath.Join(ConfDir, "appearance")