A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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}