12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559 |
- // 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 model
- import (
- "bytes"
- "errors"
- "fmt"
- "path/filepath"
- "runtime/debug"
- "strings"
- "sync"
- "sync/atomic"
- "time"
- "github.com/88250/gulu"
- "github.com/88250/lute"
- "github.com/88250/lute/ast"
- "github.com/88250/lute/editor"
- "github.com/88250/lute/lex"
- "github.com/88250/lute/parse"
- "github.com/emirpasic/gods/sets/hashset"
- "github.com/siyuan-note/filelock"
- "github.com/siyuan-note/logging"
- "github.com/siyuan-note/siyuan/kernel/av"
- "github.com/siyuan-note/siyuan/kernel/cache"
- "github.com/siyuan-note/siyuan/kernel/filesys"
- "github.com/siyuan-note/siyuan/kernel/sql"
- "github.com/siyuan-note/siyuan/kernel/treenode"
- "github.com/siyuan-note/siyuan/kernel/util"
- )
- func IsFoldHeading(transactions *[]*Transaction) bool {
- for _, tx := range *transactions {
- for _, op := range tx.DoOperations {
- if "foldHeading" == op.Action {
- return true
- }
- }
- }
- return false
- }
- func IsUnfoldHeading(transactions *[]*Transaction) bool {
- for _, tx := range *transactions {
- for _, op := range tx.DoOperations {
- if "unfoldHeading" == op.Action {
- return true
- }
- }
- }
- return false
- }
- func IsMoveOutlineHeading(transactions *[]*Transaction) bool {
- for _, tx := range *transactions {
- for _, op := range tx.DoOperations {
- if "moveOutlineHeading" == op.Action {
- return true
- }
- }
- }
- return false
- }
- func WaitForWritingFiles() {
- var printLog bool
- var lastPrintLog bool
- for i := 0; isWritingFiles(); i++ {
- time.Sleep(5 * time.Millisecond)
- if 2000 < i && !printLog { // 10s 后打日志
- logging.LogWarnf("file is writing: \n%s", logging.ShortStack())
- printLog = true
- }
- if 12000 < i && !lastPrintLog { // 60s 后打日志
- logging.LogWarnf("file is still writing")
- lastPrintLog = true
- }
- }
- }
- var (
- txQueue = make(chan *Transaction, 7)
- flushLock = sync.Mutex{}
- )
- func isWritingFiles() bool {
- time.Sleep(time.Duration(50) * time.Millisecond)
- return 0 < len(txQueue)
- }
- func init() {
- go func() {
- for {
- select {
- case tx := <-txQueue:
- flushTx(tx)
- }
- }
- }()
- }
- func flushTx(tx *Transaction) {
- defer logging.Recover()
- flushLock.Lock()
- defer flushLock.Unlock()
- start := time.Now()
- if txErr := performTx(tx); nil != txErr {
- switch txErr.code {
- case TxErrCodeBlockNotFound:
- util.PushTxErr("Transaction failed", txErr.code, nil)
- return
- case TxErrCodeDataIsSyncing:
- util.PushMsg(Conf.Language(222), 5000)
- default:
- txData, _ := gulu.JSON.MarshalJSON(tx)
- logging.LogFatalf(logging.ExitCodeFatal, "transaction failed [%d]: %s\n tx [%s]", txErr.code, txErr.msg, txData)
- }
- }
- elapsed := time.Now().Sub(start).Milliseconds()
- if 0 < len(tx.DoOperations) {
- if 2000 < elapsed {
- logging.LogWarnf("op tx [%dms]", elapsed)
- }
- }
- }
- func PerformTransactions(transactions *[]*Transaction) {
- for _, tx := range *transactions {
- tx.m = &sync.Mutex{}
- txQueue <- tx
- }
- return
- }
- const (
- TxErrCodeBlockNotFound = 0
- TxErrCodeDataIsSyncing = 1
- TxErrCodeWriteTree = 2
- TxErrWriteAttributeView = 3
- )
- type TxErr struct {
- code int
- msg string
- id string
- }
- func performTx(tx *Transaction) (ret *TxErr) {
- if 1 > len(tx.DoOperations) {
- return
- }
- //os.MkdirAll("pprof", 0755)
- //cpuProfile, _ := os.Create("pprof/cpu_profile_tx")
- //pprof.StartCPUProfile(cpuProfile)
- //defer pprof.StopCPUProfile()
- var err error
- if err = tx.begin(); nil != err {
- if strings.Contains(err.Error(), "database is closed") {
- return
- }
- logging.LogErrorf("begin tx failed: %s", err)
- ret = &TxErr{msg: err.Error()}
- return
- }
- defer func() {
- if e := recover(); nil != e {
- stack := debug.Stack()
- msg := fmt.Sprintf("PANIC RECOVERED: %v\n\t%s\n", e, stack)
- logging.LogErrorf(msg)
- if 1 == tx.state.Load() {
- tx.rollback()
- return
- }
- }
- }()
- for _, op := range tx.DoOperations {
- switch op.Action {
- case "create":
- ret = tx.doCreate(op)
- case "update":
- ret = tx.doUpdate(op)
- case "insert":
- ret = tx.doInsert(op)
- case "delete":
- ret = tx.doDelete(op)
- case "move":
- ret = tx.doMove(op)
- case "moveOutlineHeading":
- ret = tx.doMoveOutlineHeading(op)
- case "append":
- ret = tx.doAppend(op)
- case "appendInsert":
- ret = tx.doAppendInsert(op)
- case "prependInsert":
- ret = tx.doPrependInsert(op)
- case "foldHeading":
- ret = tx.doFoldHeading(op)
- case "unfoldHeading":
- ret = tx.doUnfoldHeading(op)
- case "setAttrs":
- ret = tx.doSetAttrs(op)
- case "doUpdateUpdated":
- ret = tx.doUpdateUpdated(op)
- case "addFlashcards":
- ret = tx.doAddFlashcards(op)
- case "removeFlashcards":
- ret = tx.doRemoveFlashcards(op)
- case "setAttrViewName":
- ret = tx.doSetAttrViewName(op)
- case "setAttrViewFilters":
- ret = tx.doSetAttrViewFilters(op)
- case "setAttrViewSorts":
- ret = tx.doSetAttrViewSorts(op)
- case "setAttrViewPageSize":
- ret = tx.doSetAttrViewPageSize(op)
- case "setAttrViewColWidth":
- ret = tx.doSetAttrViewColumnWidth(op)
- case "setAttrViewColWrap":
- ret = tx.doSetAttrViewColumnWrap(op)
- case "setAttrViewColHidden":
- ret = tx.doSetAttrViewColumnHidden(op)
- case "setAttrViewColPin":
- ret = tx.doSetAttrViewColumnPin(op)
- case "setAttrViewColIcon":
- ret = tx.doSetAttrViewColumnIcon(op)
- case "insertAttrViewBlock":
- ret = tx.doInsertAttrViewBlock(op)
- case "removeAttrViewBlock":
- ret = tx.doRemoveAttrViewBlock(op)
- case "addAttrViewCol":
- ret = tx.doAddAttrViewColumn(op)
- case "updateAttrViewCol":
- ret = tx.doUpdateAttrViewColumn(op)
- case "removeAttrViewCol":
- ret = tx.doRemoveAttrViewColumn(op)
- case "sortAttrViewRow":
- ret = tx.doSortAttrViewRow(op)
- case "sortAttrViewCol":
- ret = tx.doSortAttrViewColumn(op)
- case "sortAttrViewKey":
- ret = tx.doSortAttrViewKey(op)
- case "updateAttrViewCell":
- ret = tx.doUpdateAttrViewCell(op)
- case "updateAttrViewColOptions":
- ret = tx.doUpdateAttrViewColOptions(op)
- case "removeAttrViewColOption":
- ret = tx.doRemoveAttrViewColOption(op)
- case "updateAttrViewColOption":
- ret = tx.doUpdateAttrViewColOption(op)
- case "setAttrViewColCalc":
- ret = tx.doSetAttrViewColCalc(op)
- case "updateAttrViewColNumberFormat":
- ret = tx.doUpdateAttrViewColNumberFormat(op)
- case "replaceAttrViewBlock":
- ret = tx.doReplaceAttrViewBlock(op)
- case "updateAttrViewColTemplate":
- ret = tx.doUpdateAttrViewColTemplate(op)
- case "addAttrViewView":
- ret = tx.doAddAttrViewView(op)
- case "removeAttrViewView":
- ret = tx.doRemoveAttrViewView(op)
- case "setAttrViewViewName":
- ret = tx.doSetAttrViewViewName(op)
- case "setAttrViewViewIcon":
- ret = tx.doSetAttrViewViewIcon(op)
- case "duplicateAttrViewView":
- ret = tx.doDuplicateAttrViewView(op)
- case "sortAttrViewView":
- ret = tx.doSortAttrViewView(op)
- case "updateAttrViewColRelation":
- ret = tx.doUpdateAttrViewColRelation(op)
- case "updateAttrViewColRollup":
- ret = tx.doUpdateAttrViewColRollup(op)
- case "hideAttrViewName":
- ret = tx.doHideAttrViewName(op)
- case "setAttrViewColDate":
- ret = tx.doSetAttrViewColDate(op)
- case "unbindAttrViewBlock":
- ret = tx.doUnbindAttrViewBlock(op)
- case "duplicateAttrViewKey":
- ret = tx.doDuplicateAttrViewKey(op)
- }
- if nil != ret {
- tx.rollback()
- return
- }
- }
- if cr := tx.commit(); nil != cr {
- logging.LogErrorf("commit tx failed: %s", cr)
- return &TxErr{msg: cr.Error()}
- }
- return
- }
- func (tx *Transaction) doMove(operation *Operation) (ret *TxErr) {
- var err error
- id := operation.ID
- srcTree, err := tx.loadTree(id)
- if nil != err {
- logging.LogErrorf("load tree [%s] failed: %s", id, err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- srcNode := treenode.GetNodeInTree(srcTree, id)
- if nil == srcNode {
- logging.LogErrorf("get node [%s] in tree [%s] failed", id, srcTree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- var headingChildren []*ast.Node
- if isMovingFoldHeading := ast.NodeHeading == srcNode.Type && "1" == srcNode.IALAttr("fold"); isMovingFoldHeading {
- headingChildren = treenode.HeadingChildren(srcNode)
- // Blocks below other non-folded headings are no longer moved when moving a folded heading https://github.com/siyuan-note/siyuan/issues/8321
- headingChildren = treenode.GetHeadingFold(headingChildren)
- }
- refreshHeadingChildrenUpdated(srcNode, time.Now().Format("20060102150405"))
- var srcEmptyList *ast.Node
- if ast.NodeListItem == srcNode.Type && srcNode.Parent.FirstChild == srcNode && srcNode.Parent.LastChild == srcNode {
- // 列表中唯一的列表项被移除后,该列表就为空了
- srcEmptyList = srcNode.Parent
- }
- targetPreviousID := operation.PreviousID
- targetParentID := operation.ParentID
- if "" != targetPreviousID {
- if id == targetPreviousID {
- return
- }
- var targetTree *parse.Tree
- targetTree, err = tx.loadTree(targetPreviousID)
- if nil != err {
- logging.LogErrorf("load tree [%s] failed: %s", targetPreviousID, err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: targetPreviousID}
- }
- isSameTree := srcTree.ID == targetTree.ID
- if isSameTree {
- targetTree = srcTree
- }
- targetNode := treenode.GetNodeInTree(targetTree, targetPreviousID)
- if nil == targetNode {
- logging.LogErrorf("get node [%s] in tree [%s] failed", targetPreviousID, targetTree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: targetPreviousID}
- }
- if ast.NodeHeading == targetNode.Type && "1" == targetNode.IALAttr("fold") {
- targetChildren := treenode.HeadingChildren(targetNode)
- targetChildren = treenode.GetHeadingFold(targetChildren)
- if l := len(targetChildren); 0 < l {
- targetNode = targetChildren[l-1]
- }
- }
- if isMovingFoldHeadingIntoSelf(targetNode, headingChildren) {
- return
- }
- for i := len(headingChildren) - 1; -1 < i; i-- {
- c := headingChildren[i]
- targetNode.InsertAfter(c)
- }
- targetNode.InsertAfter(srcNode)
- if nil != srcEmptyList {
- srcEmptyList.Unlink()
- }
- refreshHeadingChildrenUpdated(srcNode, time.Now().Format("20060102150405"))
- refreshUpdated(srcNode)
- refreshUpdated(srcTree.Root)
- if err = tx.writeTree(srcTree); nil != err {
- return
- }
- if !isSameTree {
- if err = tx.writeTree(targetTree); nil != err {
- return
- }
- }
- return
- }
- if id == targetParentID {
- return
- }
- targetTree, err := tx.loadTree(targetParentID)
- if nil != err {
- logging.LogErrorf("load tree [%s] failed: %s", targetParentID, err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: targetParentID}
- }
- isSameTree := srcTree.ID == targetTree.ID
- if isSameTree {
- targetTree = srcTree
- }
- targetNode := treenode.GetNodeInTree(targetTree, targetParentID)
- if nil == targetNode {
- logging.LogErrorf("get node [%s] in tree [%s] failed", targetParentID, targetTree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: targetParentID}
- }
- if isMovingFoldHeadingIntoSelf(targetNode, headingChildren) {
- return
- }
- processed := false
- if ast.NodeSuperBlock == targetNode.Type {
- // 在布局节点后插入
- targetNode = targetNode.FirstChild.Next
- for i := len(headingChildren) - 1; -1 < i; i-- {
- c := headingChildren[i]
- targetNode.InsertAfter(c)
- }
- targetNode.InsertAfter(srcNode)
- if nil != srcEmptyList {
- srcEmptyList.Unlink()
- }
- processed = true
- } else if ast.NodeListItem == targetNode.Type {
- if 3 == targetNode.ListData.Typ {
- // 在任务列表标记节点后插入
- targetNode = targetNode.FirstChild
- for i := len(headingChildren) - 1; -1 < i; i-- {
- c := headingChildren[i]
- targetNode.InsertAfter(c)
- }
- targetNode.InsertAfter(srcNode)
- if nil != srcEmptyList {
- srcEmptyList.Unlink()
- }
- processed = true
- }
- }
- if !processed {
- for i := len(headingChildren) - 1; -1 < i; i-- {
- c := headingChildren[i]
- targetNode.PrependChild(c)
- }
- targetNode.PrependChild(srcNode)
- if nil != srcEmptyList {
- srcEmptyList.Unlink()
- }
- }
- refreshHeadingChildrenUpdated(srcNode, time.Now().Format("20060102150405"))
- refreshUpdated(srcNode)
- refreshUpdated(srcTree.Root)
- if err = tx.writeTree(srcTree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id}
- }
- if !isSameTree {
- if err = tx.writeTree(targetTree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id}
- }
- }
- return
- }
- func isMovingFoldHeadingIntoSelf(targetNode *ast.Node, headingChildren []*ast.Node) bool {
- for _, headingChild := range headingChildren {
- if headingChild.ID == targetNode.ID {
- // 不能将折叠标题移动到自己下方节点的前或后 https://github.com/siyuan-note/siyuan/issues/7163
- return true
- }
- }
- return false
- }
- func (tx *Transaction) doPrependInsert(operation *Operation) (ret *TxErr) {
- var err error
- block := treenode.GetBlockTree(operation.ParentID)
- if nil == block {
- logging.LogWarnf("not found block [%s]", operation.ParentID)
- util.ReloadUI() // 比如分屏后编辑器状态不一致,这里强制重新载入界面
- return
- }
- tree, err := tx.loadTree(block.ID)
- if nil != err {
- msg := fmt.Sprintf("load tree [%s] failed: %s", block.ID, err)
- logging.LogErrorf(msg)
- return &TxErr{code: TxErrCodeBlockNotFound, id: block.ID}
- }
- data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "")
- subTree := tx.luteEngine.BlockDOM2Tree(data)
- insertedNode := subTree.Root.FirstChild
- if nil == insertedNode {
- return &TxErr{code: TxErrCodeBlockNotFound, msg: "invalid data tree", id: block.ID}
- }
- var remains []*ast.Node
- for remain := insertedNode.Next; nil != remain; remain = remain.Next {
- if ast.NodeKramdownBlockIAL != remain.Type {
- if "" == remain.ID {
- remain.ID = ast.NewNodeID()
- remain.SetIALAttr("id", remain.ID)
- }
- remains = append(remains, remain)
- }
- }
- if "" == insertedNode.ID {
- insertedNode.ID = ast.NewNodeID()
- insertedNode.SetIALAttr("id", insertedNode.ID)
- }
- node := treenode.GetNodeInTree(tree, operation.ParentID)
- if nil == node {
- logging.LogErrorf("get node [%s] in tree [%s] failed", operation.ParentID, tree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: operation.ParentID}
- }
- isContainer := node.IsContainerBlock()
- for i := len(remains) - 1; 0 <= i; i-- {
- remain := remains[i]
- if isContainer {
- if ast.NodeListItem == node.Type && 3 == node.ListData.Typ {
- node.FirstChild.InsertAfter(remain)
- } else if ast.NodeSuperBlock == node.Type {
- node.FirstChild.Next.InsertAfter(remain)
- } else {
- node.PrependChild(remain)
- }
- } else {
- node.InsertAfter(remain)
- }
- }
- if isContainer {
- if ast.NodeListItem == node.Type && 3 == node.ListData.Typ {
- node.FirstChild.InsertAfter(insertedNode)
- } else if ast.NodeSuperBlock == node.Type {
- node.FirstChild.Next.InsertAfter(insertedNode)
- } else {
- node.PrependChild(insertedNode)
- }
- } else {
- node.InsertAfter(insertedNode)
- }
- createdUpdated(insertedNode)
- tx.nodes[insertedNode.ID] = insertedNode
- if err = tx.writeTree(tree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: block.ID}
- }
- operation.ID = insertedNode.ID
- operation.ParentID = insertedNode.Parent.ID
- // 将 prependInsert 转换为 insert 推送
- operation.Action = "insert"
- if nil != insertedNode.Previous {
- operation.PreviousID = insertedNode.Previous.ID
- }
- return
- }
- func (tx *Transaction) doAppendInsert(operation *Operation) (ret *TxErr) {
- var err error
- block := treenode.GetBlockTree(operation.ParentID)
- if nil == block {
- logging.LogWarnf("not found block [%s]", operation.ParentID)
- util.ReloadUI() // 比如分屏后编辑器状态不一致,这里强制重新载入界面
- return
- }
- tree, err := tx.loadTree(block.ID)
- if nil != err {
- msg := fmt.Sprintf("load tree [%s] failed: %s", block.ID, err)
- logging.LogErrorf(msg)
- return &TxErr{code: TxErrCodeBlockNotFound, id: block.ID}
- }
- data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "")
- subTree := tx.luteEngine.BlockDOM2Tree(data)
- insertedNode := subTree.Root.FirstChild
- if nil == insertedNode {
- return &TxErr{code: TxErrCodeBlockNotFound, msg: "invalid data tree", id: block.ID}
- }
- if "" == insertedNode.ID {
- insertedNode.ID = ast.NewNodeID()
- insertedNode.SetIALAttr("id", insertedNode.ID)
- }
- var toInserts []*ast.Node
- for toInsert := insertedNode; nil != toInsert; toInsert = toInsert.Next {
- if ast.NodeKramdownBlockIAL != toInsert.Type {
- if "" == toInsert.ID {
- toInsert.ID = ast.NewNodeID()
- toInsert.SetIALAttr("id", toInsert.ID)
- }
- toInserts = append(toInserts, toInsert)
- }
- }
- node := treenode.GetNodeInTree(tree, operation.ParentID)
- if nil == node {
- logging.LogErrorf("get node [%s] in tree [%s] failed", operation.ParentID, tree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: operation.ParentID}
- }
- isContainer := node.IsContainerBlock()
- for i := 0; i < len(toInserts); i++ {
- toInsert := toInserts[i]
- if isContainer {
- if ast.NodeList == node.Type {
- // 列表下只能挂列表项,所以这里需要分情况处理 https://github.com/siyuan-note/siyuan/issues/9955
- if ast.NodeList == toInsert.Type {
- var childLis []*ast.Node
- for childLi := toInsert.FirstChild; nil != childLi; childLi = childLi.Next {
- childLis = append(childLis, childLi)
- }
- for _, childLi := range childLis {
- node.AppendChild(childLi)
- }
- } else {
- newLiID := ast.NewNodeID()
- newLi := &ast.Node{ID: newLiID, Type: ast.NodeListItem, ListData: &ast.ListData{Typ: node.ListData.Typ}}
- newLi.SetIALAttr("id", newLiID)
- node.AppendChild(newLi)
- newLi.AppendChild(toInsert)
- }
- } else if ast.NodeSuperBlock == node.Type {
- node.LastChild.InsertBefore(toInsert)
- } else {
- node.AppendChild(toInsert)
- }
- } else {
- node.InsertAfter(toInsert)
- }
- }
- createdUpdated(insertedNode)
- tx.nodes[insertedNode.ID] = insertedNode
- if err = tx.writeTree(tree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: block.ID}
- }
- operation.ID = insertedNode.ID
- operation.ParentID = insertedNode.Parent.ID
- // 将 appendInsert 转换为 insert 推送
- operation.Action = "insert"
- if nil != insertedNode.Previous {
- operation.PreviousID = insertedNode.Previous.ID
- }
- return
- }
- func (tx *Transaction) doAppend(operation *Operation) (ret *TxErr) {
- var err error
- id := operation.ID
- srcTree, err := tx.loadTree(id)
- if nil != err {
- logging.LogErrorf("load tree [%s] failed: %s", id, err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- srcNode := treenode.GetNodeInTree(srcTree, id)
- if nil == srcNode {
- logging.LogErrorf("get node [%s] in tree [%s] failed", id, srcTree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- if ast.NodeDocument == srcNode.Type {
- logging.LogWarnf("can't append a root to another root")
- return
- }
- var headingChildren []*ast.Node
- if isMovingFoldHeading := ast.NodeHeading == srcNode.Type && "1" == srcNode.IALAttr("fold"); isMovingFoldHeading {
- headingChildren = treenode.HeadingChildren(srcNode)
- }
- var srcEmptyList, targetNewList *ast.Node
- if ast.NodeListItem == srcNode.Type {
- targetNewListID := ast.NewNodeID()
- targetNewList = &ast.Node{ID: targetNewListID, Type: ast.NodeList, ListData: &ast.ListData{Typ: srcNode.ListData.Typ}}
- targetNewList.SetIALAttr("id", targetNewListID)
- if srcNode.Parent.FirstChild == srcNode && srcNode.Parent.LastChild == srcNode {
- // 列表中唯一的列表项被移除后,该列表就为空了
- srcEmptyList = srcNode.Parent
- }
- }
- targetRootID := operation.ParentID
- if id == targetRootID {
- logging.LogWarnf("target root id is nil")
- return
- }
- targetTree, err := tx.loadTree(targetRootID)
- if nil != err {
- logging.LogErrorf("load tree [%s] failed: %s", targetRootID, err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: targetRootID}
- }
- isSameTree := srcTree.ID == targetTree.ID
- if isSameTree {
- targetTree = srcTree
- }
- targetRoot := targetTree.Root
- if nil != targetNewList {
- if nil != targetRoot.LastChild {
- if ast.NodeList != targetRoot.LastChild.Type {
- targetNewList.AppendChild(srcNode)
- targetRoot.AppendChild(targetNewList)
- } else {
- targetRoot.LastChild.AppendChild(srcNode)
- }
- } else {
- targetRoot.AppendChild(srcNode)
- }
- } else {
- targetRoot.AppendChild(srcNode)
- }
- for _, c := range headingChildren {
- targetRoot.AppendChild(c)
- }
- if nil != srcEmptyList {
- srcEmptyList.Unlink()
- }
- if err = tx.writeTree(srcTree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id}
- }
- if !isSameTree {
- if err = tx.writeTree(targetTree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id}
- }
- }
- return
- }
- func (tx *Transaction) doDelete(operation *Operation) (ret *TxErr) {
- // logging.LogInfof("commit delete [%+v]", operation)
- var err error
- id := operation.ID
- tree, err := tx.loadTree(id)
- if nil != err {
- if errors.Is(err, ErrBlockNotFound) {
- // move 以后这里会空,算作正常情况
- return
- }
- msg := fmt.Sprintf("load tree [%s] failed: %s", id, err)
- logging.LogErrorf(msg)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- node := treenode.GetNodeInTree(tree, id)
- if nil == node {
- return nil // move 以后的情况,列表项移动导致的状态异常 https://github.com/siyuan-note/insider/issues/961
- }
- parent := node.Parent
- if nil != node.Next && ast.NodeKramdownBlockIAL == node.Next.Type && bytes.Contains(node.Next.Tokens, []byte(node.ID)) {
- // 列表块撤销状态异常 https://github.com/siyuan-note/siyuan/issues/3985
- node.Next.Unlink()
- }
- refreshHeadingChildrenUpdated(node, time.Now().Format("20060102150405"))
- node.Unlink()
- if nil != parent && ast.NodeListItem == parent.Type && nil == parent.FirstChild {
- // 保持空列表项
- node.FirstChild = nil
- parent.AppendChild(node)
- }
- treenode.RemoveBlockTree(node.ID)
- delete(tx.nodes, node.ID)
- if err = tx.writeTree(tree); nil != err {
- return
- }
- syncDelete2AttributeView(node)
- removeAvBlockRel(node)
- return
- }
- func removeAvBlockRel(node *ast.Node) {
- var avIDs []string
- ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
- if !entering {
- return ast.WalkContinue
- }
- if ast.NodeAttributeView == n.Type {
- avID := n.AttributeViewID
- if changed := av.RemoveBlockRel(avID, n.ID, treenode.ExistBlockTree); changed {
- avIDs = append(avIDs, avID)
- }
- }
- return ast.WalkContinue
- })
- avIDs = gulu.Str.RemoveDuplicatedElem(avIDs)
- for _, avID := range avIDs {
- util.PushReloadAttrView(avID)
- }
- }
- func syncDelete2AttributeView(node *ast.Node) {
- changedAvIDs := hashset.New()
- ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
- if !entering || !n.IsBlock() {
- return ast.WalkContinue
- }
- avs := n.IALAttr(av.NodeAttrNameAvs)
- if "" == avs {
- return ast.WalkContinue
- }
- avIDs := strings.Split(avs, ",")
- for _, avID := range avIDs {
- attrView, parseErr := av.ParseAttributeView(avID)
- if nil != parseErr {
- continue
- }
- changedAv := false
- blockValues := attrView.GetBlockKeyValues()
- if nil == blockValues {
- continue
- }
- for i, blockValue := range blockValues.Values {
- if blockValue.Block.ID == n.ID {
- blockValues.Values = append(blockValues.Values[:i], blockValues.Values[i+1:]...)
- changedAv = true
- break
- }
- }
- if changedAv {
- av.SaveAttributeView(attrView)
- changedAvIDs.Add(avID)
- }
- }
- return ast.WalkContinue
- })
- for _, avID := range changedAvIDs.Values() {
- util.PushReloadAttrView(avID.(string))
- }
- }
- func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) {
- var err error
- opParentID := operation.ParentID
- block := treenode.GetBlockTree(opParentID)
- if nil == block {
- block = treenode.GetBlockTree(operation.PreviousID)
- if nil == block {
- block = treenode.GetBlockTree(operation.NextID)
- }
- }
- if nil == block {
- logging.LogWarnf("not found block [%s, %s, %s]", operation.ParentID, operation.PreviousID, operation.NextID)
- util.ReloadUI() // 比如分屏后编辑器状态不一致,这里强制重新载入界面
- return
- }
- tree, err := tx.loadTree(block.ID)
- if nil != err {
- msg := fmt.Sprintf("load tree [%s] failed: %s", block.ID, err)
- logging.LogErrorf(msg)
- return &TxErr{code: TxErrCodeBlockNotFound, id: block.ID}
- }
- data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "")
- subTree := tx.luteEngine.BlockDOM2Tree(data)
- p := block.Path
- assets := getAssetsDir(filepath.Join(util.DataDir, block.BoxID), filepath.Dir(filepath.Join(util.DataDir, block.BoxID, p)))
- isGlobalAssets := strings.HasPrefix(assets, filepath.Join(util.DataDir, "assets"))
- if !isGlobalAssets {
- // 本地资源文件需要移动到用户手动建立的 assets 下 https://github.com/siyuan-note/siyuan/issues/2410
- ast.Walk(subTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
- if !entering {
- return ast.WalkContinue
- }
- if ast.NodeLinkDest == n.Type && bytes.HasPrefix(n.Tokens, []byte("assets/")) {
- assetP := gulu.Str.FromBytes(n.Tokens)
- assetPath, e := GetAssetAbsPath(assetP)
- if nil != e {
- logging.LogErrorf("get path of asset [%s] failed: %s", assetP, err)
- return ast.WalkContinue
- }
- if !strings.HasPrefix(assetPath, filepath.Join(util.DataDir, "assets")) {
- // 非全局 assets 则跳过
- return ast.WalkContinue
- }
- // 只有全局 assets 才移动到相对 assets
- targetP := filepath.Join(assets, filepath.Base(assetPath))
- if e = filelock.Rename(assetPath, targetP); nil != err {
- logging.LogErrorf("copy path of asset from [%s] to [%s] failed: %s", assetPath, targetP, err)
- return ast.WalkContinue
- }
- }
- return ast.WalkContinue
- })
- }
- insertedNode := subTree.Root.FirstChild
- if nil == insertedNode {
- return &TxErr{code: TxErrCodeBlockNotFound, msg: "invalid data tree", id: block.ID}
- }
- var remains []*ast.Node
- for remain := insertedNode.Next; nil != remain; remain = remain.Next {
- if ast.NodeKramdownBlockIAL != remain.Type {
- if "" == remain.ID {
- remain.ID = ast.NewNodeID()
- remain.SetIALAttr("id", remain.ID)
- }
- remains = append(remains, remain)
- }
- }
- if "" == insertedNode.ID {
- insertedNode.ID = ast.NewNodeID()
- insertedNode.SetIALAttr("id", insertedNode.ID)
- }
- var node *ast.Node
- nextID := operation.NextID
- previousID := operation.PreviousID
- if "" != nextID {
- node = treenode.GetNodeInTree(tree, nextID)
- if nil == node {
- logging.LogErrorf("get node [%s] in tree [%s] failed", nextID, tree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: nextID}
- }
- if ast.NodeList == insertedNode.Type && nil != node.Parent && ast.NodeList == node.Parent.Type {
- insertedNode = insertedNode.FirstChild
- }
- node.InsertBefore(insertedNode)
- } else if "" != previousID {
- node = treenode.GetNodeInTree(tree, previousID)
- if nil == node {
- logging.LogErrorf("get node [%s] in tree [%s] failed", previousID, tree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: previousID}
- }
- if ast.NodeHeading == node.Type && "1" == node.IALAttr("fold") {
- children := treenode.HeadingChildren(node)
- if l := len(children); 0 < l {
- node = children[l-1]
- }
- }
- if ast.NodeList == insertedNode.Type && nil != node.Parent && ast.NodeList == node.Parent.Type {
- insertedNode = insertedNode.FirstChild
- }
- for i := len(remains) - 1; 0 <= i; i-- {
- remain := remains[i]
- node.InsertAfter(remain)
- }
- node.InsertAfter(insertedNode)
- } else {
- node = treenode.GetNodeInTree(tree, operation.ParentID)
- if nil == node {
- logging.LogErrorf("get node [%s] in tree [%s] failed", operation.ParentID, tree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: operation.ParentID}
- }
- if ast.NodeSuperBlock == node.Type {
- // 在布局节点后插入
- node.FirstChild.Next.InsertAfter(insertedNode)
- } else {
- if ast.NodeList == insertedNode.Type && nil != insertedNode.FirstChild && operation.ID == insertedNode.FirstChild.ID && operation.ID != insertedNode.ID {
- // 将一个列表项移动到另一个列表的第一项时 https://github.com/siyuan-note/siyuan/issues/2341
- insertedNode = insertedNode.FirstChild
- }
- if ast.NodeListItem == node.Type && 3 == node.ListData.Typ {
- // 在任务列表标记节点后插入
- node.FirstChild.InsertAfter(insertedNode)
- for _, remain := range remains {
- node.FirstChild.InsertAfter(remain)
- }
- } else {
- for i := len(remains) - 1; 0 <= i; i-- {
- remain := remains[i]
- node.PrependChild(remain)
- }
- node.PrependChild(insertedNode)
- }
- }
- }
- refreshHeadingChildrenUpdated(insertedNode, time.Now().Format("20060102150405"))
- createdUpdated(insertedNode)
- tx.nodes[insertedNode.ID] = insertedNode
- if err = tx.writeTree(tree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: block.ID}
- }
- upsertAvBlockRel(insertedNode)
- operation.ID = insertedNode.ID
- operation.ParentID = insertedNode.Parent.ID
- checkUpsertInUserGuide(tree)
- return
- }
- func (tx *Transaction) doUpdate(operation *Operation) (ret *TxErr) {
- id := operation.ID
- tree, err := tx.loadTree(id)
- if nil != err {
- if errors.Is(err, ErrBlockNotFound) {
- logging.LogWarnf("not found block [%s]", id)
- return
- }
- logging.LogErrorf("load tree [%s] failed: %s", id, err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "")
- if "" == data {
- logging.LogErrorf("update data is nil")
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- subTree := tx.luteEngine.BlockDOM2Tree(data)
- subTree.ID, subTree.Box, subTree.Path = tree.ID, tree.Box, tree.Path
- oldNode := treenode.GetNodeInTree(tree, id)
- if nil == oldNode {
- logging.LogErrorf("get node [%s] in tree [%s] failed", id, tree.Root.ID)
- return &TxErr{msg: ErrBlockNotFound.Error(), id: id}
- }
- var unlinks []*ast.Node
- ast.Walk(subTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
- if !entering {
- return ast.WalkContinue
- }
- if ast.NodeTextMark == n.Type {
- if n.IsTextMarkType("inline-math") {
- if "" == strings.TrimSpace(n.TextMarkInlineMathContent) {
- // 剔除空白的行级公式
- unlinks = append(unlinks, n)
- }
- } else if n.IsTextMarkType("block-ref") {
- sql.CacheRef(subTree, n)
- if "d" == n.TextMarkBlockRefSubtype {
- // 偶发编辑文档标题后引用处的动态锚文本不更新 https://github.com/siyuan-note/siyuan/issues/5891
- // 使用缓存的动态锚文本强制覆盖当前块中的引用节点动态锚文本
- if dRefText, ok := treenode.DynamicRefTexts.Load(n.TextMarkBlockRefID); ok && "" != dRefText {
- n.TextMarkTextContent = dRefText.(string)
- }
- }
- }
- }
- return ast.WalkContinue
- })
- for _, n := range unlinks {
- n.Unlink()
- }
- updatedNode := subTree.Root.FirstChild
- if nil == updatedNode {
- logging.LogErrorf("get fist node in sub tree [%s] failed", subTree.Root.ID)
- return &TxErr{msg: ErrBlockNotFound.Error(), id: id}
- }
- if ast.NodeList == updatedNode.Type && ast.NodeList == oldNode.Parent.Type {
- updatedNode = updatedNode.FirstChild
- }
- if oldNode.IsContainerBlock() {
- // 更新容器块的话需要考虑其子块中可能存在的折叠标题,需要把这些折叠标题的下方块移动到新节点下面
- treenode.MoveFoldHeading(updatedNode, oldNode)
- }
- refreshHeadingChildrenUpdated(oldNode, time.Now().Format("20060102150405"))
- cache.PutBlockIAL(updatedNode.ID, parse.IAL2Map(updatedNode.KramdownIAL))
- // 替换为新节点
- oldNode.InsertAfter(updatedNode)
- oldNode.Unlink()
- createdUpdated(updatedNode)
- refreshHeadingChildrenUpdated(updatedNode, updatedNode.IALAttr("updated"))
- tx.nodes[updatedNode.ID] = updatedNode
- if err = tx.writeTree(tree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id}
- }
- upsertAvBlockRel(updatedNode)
- checkUpsertInUserGuide(tree)
- return
- }
- func refreshHeadingChildrenUpdated(heading *ast.Node, updated string) {
- if nil == heading || ast.NodeHeading != heading.Type {
- return
- }
- // 将非标题块更新为标题块时需要更新下方块的 parent id
- // The parent block field of the blocks under the heading block is calculated incorrectly https://github.com/siyuan-note/siyuan/issues/9869
- children := treenode.HeadingChildren(heading)
- for _, child := range children {
- child.SetIALAttr("updated", updated)
- }
- }
- func upsertAvBlockRel(node *ast.Node) {
- var avIDs []string
- ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
- if !entering {
- return ast.WalkContinue
- }
- if ast.NodeAttributeView == n.Type {
- avID := n.AttributeViewID
- if changed := av.UpsertBlockRel(avID, n.ID); changed {
- avIDs = append(avIDs, avID)
- }
- }
- return ast.WalkContinue
- })
- avIDs = gulu.Str.RemoveDuplicatedElem(avIDs)
- for _, avID := range avIDs {
- util.PushReloadAttrView(avID)
- }
- }
- func (tx *Transaction) doUpdateUpdated(operation *Operation) (ret *TxErr) {
- id := operation.ID
- tree, err := tx.loadTree(id)
- if nil != err {
- if errors.Is(err, ErrBlockNotFound) {
- logging.LogWarnf("not found block [%s]", id)
- return
- }
- logging.LogErrorf("load tree [%s] failed: %s", id, err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- node := treenode.GetNodeInTree(tree, id)
- if nil == node {
- logging.LogErrorf("get node [%s] in tree [%s] failed", id, tree.Root.ID)
- return &TxErr{msg: ErrBlockNotFound.Error(), id: id}
- }
- node.SetIALAttr("updated", operation.Data.(string))
- createdUpdated(node)
- tx.nodes[node.ID] = node
- if err = tx.writeTree(tree); nil != err {
- return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id}
- }
- return
- }
- func (tx *Transaction) doCreate(operation *Operation) (ret *TxErr) {
- tree := operation.Data.(*parse.Tree)
- tx.writeTree(tree)
- checkUpsertInUserGuide(tree)
- return
- }
- func (tx *Transaction) doSetAttrs(operation *Operation) (ret *TxErr) {
- id := operation.ID
- tree, err := tx.loadTree(id)
- if nil != err {
- logging.LogErrorf("load tree [%s] failed: %s", id, err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- node := treenode.GetNodeInTree(tree, id)
- if nil == node {
- logging.LogErrorf("get node [%s] in tree [%s] failed", id, tree.Root.ID)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- attrs := map[string]string{}
- if err = gulu.JSON.UnmarshalJSON([]byte(operation.Data.(string)), &attrs); nil != err {
- logging.LogErrorf("unmarshal attrs failed: %s", err)
- return &TxErr{code: TxErrCodeBlockNotFound, id: id}
- }
- var invalidNames []string
- for name := range attrs {
- for i := 0; i < len(name); i++ {
- if !lex.IsASCIILetterNumHyphen(name[i]) {
- logging.LogWarnf("invalid attr name [%s]", name)
- invalidNames = append(invalidNames, name)
- }
- }
- }
- for _, name := range invalidNames {
- delete(attrs, name)
- }
- for name, value := range attrs {
- if "" == value {
- node.RemoveIALAttr(name)
- } else {
- node.SetIALAttr(name, value)
- }
- }
- if err = tx.writeTree(tree); nil != err {
- return
- }
- cache.PutBlockIAL(id, parse.IAL2Map(node.KramdownIAL))
- return
- }
- func refreshUpdated(node *ast.Node) {
- updated := util.CurrentTimeSecondsStr()
- node.SetIALAttr("updated", updated)
- parents := treenode.ParentNodesWithHeadings(node)
- for _, parent := range parents { // 更新所有父节点的更新时间字段
- parent.SetIALAttr("updated", updated)
- }
- }
- func createdUpdated(node *ast.Node) {
- created := util.TimeFromID(node.ID)
- updated := node.IALAttr("updated")
- if "" == updated {
- updated = created
- }
- if updated < created {
- updated = created // 复制粘贴块后创建时间小于更新时间 https://github.com/siyuan-note/siyuan/issues/3624
- }
- parents := treenode.ParentNodesWithHeadings(node)
- for _, parent := range parents { // 更新所有父节点的更新时间字段
- parent.SetIALAttr("updated", updated)
- cache.PutBlockIAL(parent.ID, parse.IAL2Map(parent.KramdownIAL))
- }
- }
- type Operation struct {
- Action string `json:"action"`
- Data interface{} `json:"data"`
- ID string `json:"id"`
- ParentID string `json:"parentID"`
- PreviousID string `json:"previousID"`
- NextID string `json:"nextID"`
- RetData interface{} `json:"retData"`
- BlockIDs []string `json:"blockIDs"`
- BlockID string `json:"blockID"`
- DeckID string `json:"deckID"` // 用于添加/删除闪卡
- AvID string `json:"avID"` // 属性视图 ID
- SrcIDs []string `json:"srcIDs"` // 用于从属性视图中删除行
- Srcs []map[string]interface{} `json:"srcs"` // 用于添加属性视图行(包括绑定块){id, content, isDetached}
- IsDetached bool `json:"isDetached"` // 用于标识是否未绑定块,仅存在于属性视图中
- IgnoreFillFilterVal bool `json:"ignoreFillFilter"` // 用于标识是否忽略填充筛选值
- Name string `json:"name"` // 属性视图列名
- Typ string `json:"type"` // 属性视图列类型
- Format string `json:"format"` // 属性视图列格式化
- KeyID string `json:"keyID"` // 属性视列 ID
- RowID string `json:"rowID"` // 属性视图行 ID
- IsTwoWay bool `json:"isTwoWay"` // 属性视图关联列是否是双向关系
- BackRelationKeyID string `json:"backRelationKeyID"` // 属性视图关联列回链关联列的 ID
- }
- type Transaction struct {
- Timestamp int64 `json:"timestamp"`
- DoOperations []*Operation `json:"doOperations"`
- UndoOperations []*Operation `json:"undoOperations"`
- trees map[string]*parse.Tree
- nodes map[string]*ast.Node
- luteEngine *lute.Lute
- m *sync.Mutex
- state atomic.Int32 // 0: 初始化,1:未提交,:2: 已提交,3: 已回滚
- }
- func (tx *Transaction) WaitForCommit() {
- for {
- if 1 == tx.state.Load() {
- time.Sleep(10 * time.Millisecond)
- continue
- }
- return
- }
- }
- func (tx *Transaction) begin() (err error) {
- if nil != err {
- return
- }
- tx.trees = map[string]*parse.Tree{}
- tx.nodes = map[string]*ast.Node{}
- tx.luteEngine = util.NewLute()
- tx.m.Lock()
- tx.state.Store(1)
- return
- }
- func (tx *Transaction) commit() (err error) {
- for _, tree := range tx.trees {
- if err = writeTreeUpsertQueue(tree); nil != err {
- return
- }
- var sources []interface{}
- sources = append(sources, tx)
- util.PushSaveDoc(tree.ID, "tx", sources)
- }
- refreshDynamicRefTexts(tx.nodes, tx.trees)
- IncSync()
- tx.state.Store(2)
- tx.m.Unlock()
- return
- }
- func (tx *Transaction) rollback() {
- tx.trees, tx.nodes = nil, nil
- tx.state.Store(3)
- tx.m.Unlock()
- return
- }
- func (tx *Transaction) loadTree(id string) (ret *parse.Tree, err error) {
- var rootID, box, p string
- bt := treenode.GetBlockTree(id)
- if nil == bt {
- return nil, ErrBlockNotFound
- }
- rootID = bt.RootID
- box = bt.BoxID
- p = bt.Path
- ret = tx.trees[rootID]
- if nil != ret {
- return
- }
- ret, err = filesys.LoadTree(box, p, tx.luteEngine)
- if nil != err {
- return
- }
- tx.trees[rootID] = ret
- return
- }
- func (tx *Transaction) writeTree(tree *parse.Tree) (err error) {
- tx.trees[tree.ID] = tree
- treenode.IndexBlockTree(tree)
- return
- }
- // refreshDynamicRefText 用于刷新块引用的动态锚文本。
- // 该实现依赖了数据库缓存,导致外部调用时可能需要阻塞等待数据库写入后才能获取到 refs
- func refreshDynamicRefText(updatedDefNode *ast.Node, updatedTree *parse.Tree) {
- changedDefs := map[string]*ast.Node{updatedDefNode.ID: updatedDefNode}
- changedTrees := map[string]*parse.Tree{updatedTree.ID: updatedTree}
- refreshDynamicRefTexts(changedDefs, changedTrees)
- }
- // refreshDynamicRefTexts 用于批量刷新块引用的动态锚文本。
- // 该实现依赖了数据库缓存,导致外部调用时可能需要阻塞等待数据库写入后才能获取到 refs
- func refreshDynamicRefTexts(updatedDefNodes map[string]*ast.Node, updatedTrees map[string]*parse.Tree) {
- // 1. 更新引用的动态锚文本
- treeRefNodeIDs := map[string]*hashset.Set{}
- for _, updateNode := range updatedDefNodes {
- refs := sql.GetRefsCacheByDefID(updateNode.ID)
- if nil != updateNode.Parent && ast.NodeDocument != updateNode.Parent.Type &&
- updateNode.Parent.IsContainerBlock() && (updateNode == treenode.FirstLeafBlock(updateNode.Parent)) { // 容器块下第一个子块
- var parentRefs []*sql.Ref
- if ast.NodeListItem == updateNode.Parent.Type { // 引用列表块时动态锚文本未跟随定义块内容变动 https://github.com/siyuan-note/siyuan/issues/4393
- parentRefs = sql.GetRefsCacheByDefID(updateNode.Parent.Parent.ID)
- updatedDefNodes[updateNode.Parent.ID] = updateNode.Parent
- updatedDefNodes[updateNode.Parent.Parent.ID] = updateNode.Parent.Parent
- } else {
- parentRefs = sql.GetRefsCacheByDefID(updateNode.Parent.ID)
- updatedDefNodes[updateNode.Parent.ID] = updateNode.Parent
- }
- if 0 < len(parentRefs) {
- refs = append(refs, parentRefs...)
- }
- }
- for _, ref := range refs {
- if refIDs, ok := treeRefNodeIDs[ref.RootID]; !ok {
- refIDs = hashset.New()
- refIDs.Add(ref.BlockID)
- treeRefNodeIDs[ref.RootID] = refIDs
- } else {
- refIDs.Add(ref.BlockID)
- }
- }
- }
- changedRefTree := map[string]*parse.Tree{}
- for refTreeID, refNodeIDs := range treeRefNodeIDs {
- refTree, ok := updatedTrees[refTreeID]
- if !ok {
- var err error
- refTree, err = LoadTreeByBlockID(refTreeID)
- if nil != err {
- continue
- }
- }
- var refTreeChanged bool
- ast.Walk(refTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
- if !entering {
- return ast.WalkContinue
- }
- if n.IsBlock() && refNodeIDs.Contains(n.ID) {
- changed := updateRefText(n, updatedDefNodes)
- if !refTreeChanged && changed {
- refTreeChanged = true
- }
- return ast.WalkContinue
- }
- return ast.WalkContinue
- })
- if refTreeChanged {
- changedRefTree[refTreeID] = refTree
- }
- }
- // 2. 更新属性视图主键内容
- for _, updatedDefNode := range updatedDefNodes {
- avs := updatedDefNode.IALAttr(av.NodeAttrNameAvs)
- if "" == avs {
- continue
- }
- avIDs := strings.Split(avs, ",")
- for _, avID := range avIDs {
- attrView, parseErr := av.ParseAttributeView(avID)
- if nil != parseErr {
- continue
- }
- changedAv := false
- blockValues := attrView.GetBlockKeyValues()
- if nil == blockValues {
- continue
- }
- for _, blockValue := range blockValues.Values {
- if blockValue.Block.ID == updatedDefNode.ID {
- newContent := getNodeRefText(updatedDefNode)
- if newContent != blockValue.Block.Content {
- blockValue.Block.Content = newContent
- changedAv = true
- }
- break
- }
- }
- if changedAv {
- av.SaveAttributeView(attrView)
- util.PushReloadAttrView(avID)
- }
- }
- }
- // 3. 保存变更
- for _, tree := range changedRefTree {
- indexWriteTreeUpsertQueue(tree)
- }
- }
- var updateRefTextRenameDocs = map[string]*parse.Tree{}
- var updateRefTextRenameDocLock = sync.Mutex{}
- func updateRefTextRenameDoc(renamedTree *parse.Tree) {
- updateRefTextRenameDocLock.Lock()
- updateRefTextRenameDocs[renamedTree.ID] = renamedTree
- updateRefTextRenameDocLock.Unlock()
- }
- func FlushUpdateRefTextRenameDocJob() {
- sql.WaitForWritingDatabase()
- flushUpdateRefTextRenameDoc()
- }
- func flushUpdateRefTextRenameDoc() {
- updateRefTextRenameDocLock.Lock()
- defer updateRefTextRenameDocLock.Unlock()
- for _, tree := range updateRefTextRenameDocs {
- refreshDynamicRefText(tree.Root, tree)
- }
- updateRefTextRenameDocs = map[string]*parse.Tree{}
- }
- func updateRefText(refNode *ast.Node, changedDefNodes map[string]*ast.Node) (changed bool) {
- ast.Walk(refNode, func(n *ast.Node, entering bool) ast.WalkStatus {
- if !entering {
- return ast.WalkContinue
- }
- if !treenode.IsBlockRef(n) {
- return ast.WalkContinue
- }
- defID, _, subtype := treenode.GetBlockRef(n)
- if "s" == subtype || "" == defID {
- return ast.WalkContinue
- }
- defNode := changedDefNodes[defID]
- if nil == defNode {
- return ast.WalkSkipChildren
- }
- refText := getNodeRefText(defNode)
- treenode.SetDynamicBlockRefText(n, refText)
- changed = true
- return ast.WalkContinue
- })
- return
- }
- func checkUpsertInUserGuide(tree *parse.Tree) {
- // In production mode, data reset warning pops up when editing data in the user guide https://github.com/siyuan-note/siyuan/issues/9757
- if "prod" == util.Mode && IsUserGuide(tree.Box) {
- util.PushErrMsg(Conf.Language(52), 7000)
- }
- }
|