123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- // SiYuan - Refactor your thinking
- // Copyright (c) 2020-present, b3log.org
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- package treenode
- import (
- "os"
- "path/filepath"
- "strings"
- "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
- ParentID string // 父 ID
- BoxID string // 笔记本 ID
- Path string // 文档数据路径
- HPath string // 文档可读路径
- Updated string // 更新时间
- Type string // 类型
- }
- 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)
- }
- }
- slice.m.Unlock()
- return true
- })
- 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
- }
- }
- slice.m.Unlock()
- return nil == ret
- })
- 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
- }
- slice.m.Unlock()
- return true
- })
- ret = len(roots)
- 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
- })
- 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
- }
- }
- slice.m.Unlock()
- return nil == ret
- })
- return
- }
- func GetBlockTreeRootByHPath(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 = b
- break
- }
- }
- slice.m.Unlock()
- return nil == ret
- })
- 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)
- }
- }
- slice.m.Unlock()
- return true
- })
- 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)
- }
- }
- slice.m.Unlock()
- return nil == ret
- })
- if 1 > len(roots) {
- return
- }
- for _, root := range roots {
- if root.ID == preferredParentID {
- ret = root
- return
- }
- }
- ret = roots[0]
- return
- }
- func ExistBlockTree(id string) bool {
- hash := btHash(id)
- val, ok := blockTrees.Load(hash)
- if !ok {
- return false
- }
- slice := val.(*btSlice)
- slice.m.Lock()
- _, ok = slice.data[id]
- slice.m.Unlock()
- return ok
- }
- func GetBlockTree(id string) (ret *BlockTree) {
- if "" == id {
- return
- }
- hash := btHash(id)
- val, ok := blockTrees.Load(hash)
- if !ok {
- return
- }
- slice := val.(*btSlice)
- slice.m.Lock()
- ret = slice.data[id]
- slice.m.Unlock()
- return
- }
- func SetBlockTreePath(tree *parse.Tree) {
- RemoveBlockTreesByRootID(tree.ID)
- IndexBlockTree(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()
- }
- }
- 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)
- }
- }
- slice.m.Unlock()
- return true
- })
- 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)
- }
- }
- slice.m.Unlock()
- return true
- })
- 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()
- }
- }
- 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)
- }
- }
- slice.m.Unlock()
- return true
- })
- 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)
- }
- }
- 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()
- }
- return
- }
- func RemoveBlockTree(id string) {
- val, ok := blockTrees.Load(btHash(id))
- if !ok {
- return
- }
- slice := val.(*btSlice)
- slice.m.Lock()
- delete(slice.data, id)
- slice.changed = time.Now()
- slice.m.Unlock()
- }
- 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 {
- 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)
- }
- slice := val.(*btSlice)
- slice.m.Lock()
- bt := slice.data[n.ID]
- slice.m.Unlock()
- 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 {
- children := ChildBlockNodes(n) // 需要考虑子块,因为一些操作(比如移动块)后需要同时更新子块
- changedNodes = append(changedNodes, children...)
- }
- } else {
- children := ChildBlockNodes(n)
- changedNodes = append(changedNodes, children...)
- }
- 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
- }
- 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()
- }
- 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{}
- return
- }
- entries, err := os.ReadDir(util.BlockTreePath)
- if nil != err {
- logging.LogErrorf("read block tree dir failed: %s", err)
- os.Exit(logging.ExitCodeFileSysErr)
- 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 {
- if 100 > count {
- return 100
- }
- for i := 1; i < 40; i++ {
- if count < i*500 {
- return i * 500
- }
- }
- return 500*40 + 1
- }
- func CeilBlockCount(count int) int {
- if 5000 > count {
- return 5000
- }
- for i := 1; i < 100; i++ {
- if count < i*10000 {
- return i * 10000
- }
- }
- return 10000*100 + 1
- }
- func btHash(id string) string {
- return util2.Hash([]byte(id))[0:2]
- }
|