A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 383 lines 11 kB view raw
1// SiYuan - Refactor your thinking 2// Copyright (c) 2020-present, b3log.org 3// 4// This program is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Affero General Public License as published by 6// the Free Software Foundation, either version 3 of the License, or 7// (at your option) any later version. 8// 9// This program is distributed in the hope that it will be useful, 10// but WITHOUT ANY WARRANTY; without even the implied warranty of 11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12// GNU Affero General Public License for more details. 13// 14// You should have received a copy of the GNU Affero General Public License 15// along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17package model 18 19import ( 20 "os" 21 "path/filepath" 22 "strings" 23 "time" 24 25 "github.com/88250/go-humanize" 26 "github.com/88250/gulu" 27 "github.com/88250/lute" 28 "github.com/88250/lute/ast" 29 "github.com/88250/lute/parse" 30 "github.com/88250/lute/render" 31 "github.com/emirpasic/gods/sets/hashset" 32 "github.com/siyuan-note/logging" 33 "github.com/siyuan-note/siyuan/kernel/av" 34 "github.com/siyuan-note/siyuan/kernel/filesys" 35 "github.com/siyuan-note/siyuan/kernel/sql" 36 "github.com/siyuan-note/siyuan/kernel/task" 37 "github.com/siyuan-note/siyuan/kernel/treenode" 38 "github.com/siyuan-note/siyuan/kernel/util" 39) 40 41func PushReloadPlugin(upsertPluginSet, removePluginNameSet *hashset.Set, excludeApp string) { 42 pushReloadPlugin(upsertPluginSet, removePluginNameSet, excludeApp) 43} 44 45func pushReloadPlugin(upsertPluginSet, removePluginNameSet *hashset.Set, excludeApp string) { 46 upsertPlugins, removePlugins := []string{}, []string{} 47 if nil != upsertPluginSet { 48 for _, n := range upsertPluginSet.Values() { 49 upsertPlugins = append(upsertPlugins, n.(string)) 50 } 51 } 52 if nil != removePluginNameSet { 53 for _, n := range removePluginNameSet.Values() { 54 removePlugins = append(removePlugins, n.(string)) 55 } 56 } 57 58 pushReloadPlugin0(upsertPlugins, removePlugins, excludeApp) 59} 60 61func pushReloadPlugin0(upsertPlugins, removePlugins []string, excludeApp string) { 62 logging.LogInfof("reload plugins [upserts=%v, removes=%v]", upsertPlugins, removePlugins) 63 if "" == excludeApp { 64 util.BroadcastByType("main", "reloadPlugin", 0, "", map[string]interface{}{ 65 "upsertPlugins": upsertPlugins, 66 "removePlugins": removePlugins, 67 }) 68 return 69 } 70 71 util.BroadcastByTypeAndExcludeApp(excludeApp, "main", "reloadPlugin", 0, "", map[string]interface{}{ 72 "upsertPlugins": upsertPlugins, 73 "removePlugins": removePlugins, 74 }) 75} 76 77func refreshDocInfo(tree *parse.Tree) { 78 if nil == tree { 79 return 80 } 81 82 refreshDocInfoWithSize(tree, filesys.TreeSize(tree)) 83} 84 85func refreshDocInfoWithSize(tree *parse.Tree, size uint64) { 86 if nil == tree { 87 return 88 } 89 90 refreshDocInfo0(tree, size) 91 refreshParentDocInfo(tree) 92} 93 94func refreshParentDocInfo(tree *parse.Tree) { 95 parentTree := loadParentTree(tree) 96 if nil == parentTree { 97 return 98 } 99 100 luteEngine := lute.New() 101 renderer := render.NewJSONRenderer(parentTree, luteEngine.RenderOptions) 102 data := renderer.Render() 103 refreshDocInfo0(parentTree, uint64(len(data))) 104} 105 106func refreshDocInfo0(tree *parse.Tree, size uint64) { 107 cTime, _ := time.ParseInLocation("20060102150405", tree.ID[:14], time.Local) 108 mTime := cTime 109 if updated := tree.Root.IALAttr("updated"); "" != updated { 110 if updatedTime, err := time.ParseInLocation("20060102150405", updated, time.Local); err == nil { 111 mTime = updatedTime 112 } 113 } 114 115 subFileCount := 0 116 subFiles, err := os.ReadDir(filepath.Join(util.DataDir, tree.Box, strings.TrimSuffix(tree.Path, ".sy"))) 117 if err == nil { 118 for _, subFile := range subFiles { 119 if "true" == tree.Root.IALAttr("custom-hidden") { 120 continue 121 } 122 123 if strings.HasSuffix(subFile.Name(), ".sy") { 124 subFileCount++ 125 } 126 } 127 } 128 129 docInfo := map[string]interface{}{ 130 "rootID": tree.ID, 131 "name": tree.Root.IALAttr("title"), 132 "alias": tree.Root.IALAttr("alias"), 133 "name1": tree.Root.IALAttr("name"), 134 "memo": tree.Root.IALAttr("memo"), 135 "bookmark": tree.Root.IALAttr("bookmark"), 136 "size": size, 137 "hSize": humanize.BytesCustomCeil(size, 2), 138 "mtime": mTime.Unix(), 139 "ctime": cTime.Unix(), 140 "hMtime": mTime.Format("2006-01-02 15:04:05") + ", " + util.HumanizeTime(mTime, Conf.Lang), 141 "hCtime": cTime.Format("2006-01-02 15:04:05") + ", " + util.HumanizeTime(cTime, Conf.Lang), 142 "subFileCount": subFileCount, 143 } 144 145 task.AppendAsyncTaskWithDelay(task.ReloadProtyle, 500*time.Millisecond, util.PushReloadDocInfo, docInfo) 146} 147 148func ReloadFiletree() { 149 task.AppendAsyncTaskWithDelay(task.ReloadFiletree, 200*time.Millisecond, util.PushReloadFiletree) 150} 151 152func ReloadTag() { 153 task.AppendAsyncTaskWithDelay(task.ReloadTag, 200*time.Millisecond, util.PushReloadTag) 154} 155 156func ReloadProtyle(rootID string) { 157 // 刷新关联的引用 158 defTree, _ := LoadTreeByBlockID(rootID) 159 if nil != defTree { 160 defIDs := sql.QueryChildDefIDsByRootDefID(rootID) 161 162 var defNodes []*ast.Node 163 ast.Walk(defTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { 164 if !entering || !n.IsBlock() { 165 return ast.WalkContinue 166 } 167 168 if gulu.Str.Contains(n.ID, defIDs) { 169 defNodes = append(defNodes, n) 170 } 171 return ast.WalkContinue 172 }) 173 174 for _, def := range defNodes { 175 refreshDynamicRefText(def, defTree) 176 } 177 } 178 179 // 刷新关联的嵌入块 180 refIDs := sql.QueryRefIDsByDefID(rootID, true) 181 var rootIDs []string 182 bts := treenode.GetBlockTrees(refIDs) 183 for _, bt := range bts { 184 rootIDs = append(rootIDs, bt.RootID) 185 } 186 rootIDs = gulu.Str.RemoveDuplicatedElem(rootIDs) 187 for _, id := range rootIDs { 188 task.AppendAsyncTaskWithDelay(task.ReloadProtyle, 200*time.Millisecond, util.PushReloadProtyle, id) 189 } 190 191 task.AppendAsyncTaskWithDelay(task.ReloadProtyle, 200*time.Millisecond, util.PushReloadProtyle, rootID) 192} 193 194// refreshRefCount 用于刷新定义块处的引用计数。 195func refreshRefCount(blockID string) { 196 sql.FlushQueue() 197 198 bt := treenode.GetBlockTree(blockID) 199 if nil == bt { 200 return 201 } 202 203 isDoc := bt.ID == bt.RootID 204 var rootRefIDs []string 205 var refCount, rootRefCount int 206 refIDs := sql.QueryRefIDsByDefID(bt.ID, isDoc) 207 if isDoc { 208 rootRefIDs = refIDs 209 } else { 210 rootRefIDs = sql.QueryRefIDsByDefID(bt.RootID, true) 211 } 212 refCount = len(refIDs) 213 rootRefCount = len(rootRefIDs) 214 var defIDs []string 215 if isDoc { 216 defIDs = sql.QueryChildDefIDsByRootDefID(bt.ID) 217 } else { 218 defIDs = append(defIDs, bt.ID) 219 } 220 221 util.PushSetDefRefCount(bt.RootID, blockID, defIDs, refCount, rootRefCount) 222} 223 224// refreshDynamicRefText 用于刷新块引用的动态锚文本。 225// 该实现依赖了数据库缓存,导致外部调用时可能需要阻塞等待数据库写入后才能获取到 refs 226func refreshDynamicRefText(updatedDefNode *ast.Node, updatedTree *parse.Tree) { 227 changedDefs := map[string]*ast.Node{updatedDefNode.ID: updatedDefNode} 228 changedTrees := map[string]*parse.Tree{updatedTree.ID: updatedTree} 229 refreshDynamicRefTexts(changedDefs, changedTrees) 230} 231 232// refreshDynamicRefTexts 用于批量刷新块引用的动态锚文本。 233// 该实现依赖了数据库缓存,导致外部调用时可能需要阻塞等待数据库写入后才能获取到 refs 234func refreshDynamicRefTexts(updatedDefNodes map[string]*ast.Node, updatedTrees map[string]*parse.Tree) { 235 for i := 0; i < 7; i++ { 236 updatedRefNodes, updatedRefTrees := refreshDynamicRefTexts0(updatedDefNodes, updatedTrees) 237 if 1 > len(updatedRefNodes) { 238 break 239 } 240 updatedDefNodes, updatedTrees = updatedRefNodes, updatedRefTrees 241 } 242} 243 244func refreshDynamicRefTexts0(updatedDefNodes map[string]*ast.Node, updatedTrees map[string]*parse.Tree) (updatedRefNodes map[string]*ast.Node, updatedRefTrees map[string]*parse.Tree) { 245 updatedRefNodes = map[string]*ast.Node{} 246 updatedRefTrees = map[string]*parse.Tree{} 247 248 // 1. 更新引用的动态锚文本 249 treeRefNodeIDs := map[string]*hashset.Set{} 250 var changedNodes []*ast.Node 251 var refs []*sql.Ref 252 for _, updateNode := range updatedDefNodes { 253 refs, changedNodes = getRefsCacheByDefNode(updateNode) 254 for _, ref := range refs { 255 if refIDs, ok := treeRefNodeIDs[ref.RootID]; !ok { 256 refIDs = hashset.New() 257 refIDs.Add(ref.BlockID) 258 treeRefNodeIDs[ref.RootID] = refIDs 259 } else { 260 refIDs.Add(ref.BlockID) 261 } 262 } 263 } 264 for _, n := range changedNodes { 265 updatedDefNodes[n.ID] = n 266 } 267 268 changedRefTree := map[string]*parse.Tree{} 269 270 for refTreeID, refNodeIDs := range treeRefNodeIDs { 271 refTree, ok := updatedTrees[refTreeID] 272 if !ok { 273 var err error 274 refTree, err = LoadTreeByBlockID(refTreeID) 275 if err != nil { 276 continue 277 } 278 } 279 280 var refTreeChanged bool 281 ast.Walk(refTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { 282 if !entering { 283 return ast.WalkContinue 284 } 285 286 if n.IsBlock() && refNodeIDs.Contains(n.ID) { 287 changed, changedDefNodes := updateRefText(n, updatedDefNodes) 288 if !refTreeChanged && changed { 289 refTreeChanged = true 290 updatedRefNodes[n.ID] = n 291 updatedRefTrees[refTreeID] = refTree 292 } 293 294 // 推送动态锚文本节点刷新 295 for _, defNode := range changedDefNodes { 296 switch defNode.refType { 297 case "ref-d": 298 task.AppendAsyncTaskWithDelay(task.SetRefDynamicText, 200*time.Millisecond, util.PushSetRefDynamicText, refTreeID, n.ID, defNode.id, defNode.refText) 299 } 300 } 301 return ast.WalkContinue 302 } 303 return ast.WalkContinue 304 }) 305 306 if refTreeChanged { 307 changedRefTree[refTreeID] = refTree 308 sql.UpdateRefsTreeQueue(refTree) 309 } 310 } 311 312 // 2. 更新属性视图主键内容 313 updateAttributeViewBlockText(updatedDefNodes) 314 315 // 3. 保存变更 316 for _, tree := range changedRefTree { 317 indexWriteTreeUpsertQueue(tree) 318 } 319 return 320} 321 322func updateAttributeViewBlockText(updatedDefNodes map[string]*ast.Node) { 323 var parents []*ast.Node 324 for _, updatedDefNode := range updatedDefNodes { 325 for parent := updatedDefNode.Parent; nil != parent && ast.NodeDocument != parent.Type; parent = parent.Parent { 326 parents = append(parents, parent) 327 } 328 } 329 for _, parent := range parents { 330 updatedDefNodes[parent.ID] = parent 331 } 332 333 for _, updatedDefNode := range updatedDefNodes { 334 avs := updatedDefNode.IALAttr(av.NodeAttrNameAvs) 335 if "" == avs { 336 continue 337 } 338 339 avIDs := strings.Split(avs, ",") 340 for _, avID := range avIDs { 341 attrView, parseErr := av.ParseAttributeView(avID) 342 if nil != parseErr { 343 continue 344 } 345 346 changedAv := false 347 blockValues := attrView.GetBlockKeyValues() 348 if nil == blockValues { 349 continue 350 } 351 352 for _, blockValue := range blockValues.Values { 353 if blockValue.Block.ID == updatedDefNode.ID { 354 newIcon, newContent := getNodeAvBlockText(updatedDefNode, avID) 355 if newIcon != blockValue.Block.Icon { 356 blockValue.Block.Icon = newIcon 357 changedAv = true 358 } 359 if newContent != blockValue.Block.Content { 360 blockValue.Block.Content = util.UnescapeHTML(newContent) 361 changedAv = true 362 } 363 break 364 } 365 } 366 if changedAv { 367 av.SaveAttributeView(attrView) 368 ReloadAttrView(avID) 369 370 refreshRelatedSrcAvs(avID) 371 } 372 } 373 } 374} 375 376// ReloadAttrView 用于重新加载属性视图。 377func ReloadAttrView(avID string) { 378 task.AppendAsyncTaskWithDelay(task.ReloadAttributeView, 200*time.Millisecond, pushReloadAttrView, avID) 379} 380 381func pushReloadAttrView(avID string) { 382 util.BroadcastByType("protyle", "refreshAttributeView", 0, "", map[string]interface{}{"id": avID}) 383}