index.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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. package model
  17. import (
  18. "bytes"
  19. "fmt"
  20. "path/filepath"
  21. "runtime"
  22. "runtime/debug"
  23. "strings"
  24. "sync"
  25. "time"
  26. "github.com/88250/gulu"
  27. "github.com/88250/lute/ast"
  28. "github.com/88250/lute/html"
  29. "github.com/88250/lute/parse"
  30. "github.com/dustin/go-humanize"
  31. "github.com/panjf2000/ants/v2"
  32. "github.com/siyuan-note/eventbus"
  33. "github.com/siyuan-note/filelock"
  34. "github.com/siyuan-note/logging"
  35. "github.com/siyuan-note/siyuan/kernel/cache"
  36. "github.com/siyuan-note/siyuan/kernel/filesys"
  37. "github.com/siyuan-note/siyuan/kernel/sql"
  38. "github.com/siyuan-note/siyuan/kernel/task"
  39. "github.com/siyuan-note/siyuan/kernel/treenode"
  40. "github.com/siyuan-note/siyuan/kernel/util"
  41. )
  42. func (box *Box) Unindex() {
  43. task.AppendTask(task.DatabaseIndex, unindex, box.ID)
  44. }
  45. func unindex(boxID string) {
  46. ids := treenode.RemoveBlockTreesByBoxID(boxID)
  47. RemoveRecentDoc(ids)
  48. sql.DeleteBoxQueue(boxID)
  49. }
  50. func (box *Box) Index() {
  51. task.AppendTask(task.DatabaseIndex, index, box.ID)
  52. task.AppendTask(task.DatabaseIndexRef, IndexRefs)
  53. }
  54. func index(boxID string) {
  55. box := Conf.Box(boxID)
  56. if nil == box {
  57. return
  58. }
  59. util.SetBootDetails("Listing files...")
  60. files := box.ListFiles("/")
  61. boxLen := len(Conf.GetOpenedBoxes())
  62. if 1 > boxLen {
  63. boxLen = 1
  64. }
  65. bootProgressPart := 30.0 / float64(boxLen) / float64(len(files))
  66. start := time.Now()
  67. luteEngine := util.NewLute()
  68. var treeCount int
  69. var treeSize int64
  70. i := 0
  71. util.PushStatusBar(fmt.Sprintf("["+html.EscapeString(box.Name)+"] "+Conf.Language(64), len(files)))
  72. poolSize := runtime.NumCPU()
  73. if 4 < poolSize {
  74. poolSize = 4
  75. }
  76. waitGroup := &sync.WaitGroup{}
  77. p, _ := ants.NewPoolWithFunc(poolSize, func(arg interface{}) {
  78. defer waitGroup.Done()
  79. file := arg.(*FileInfo)
  80. tree, err := filesys.LoadTree(box.ID, file.path, luteEngine)
  81. if nil != err {
  82. logging.LogErrorf("read box [%s] tree [%s] failed: %s", box.ID, file.path, err)
  83. return
  84. }
  85. docIAL := parse.IAL2MapUnEsc(tree.Root.KramdownIAL)
  86. if "" == docIAL["updated"] { // 早期的数据可能没有 updated 属性,这里进行订正
  87. updated := util.TimeFromID(tree.Root.ID)
  88. tree.Root.SetIALAttr("updated", updated)
  89. docIAL["updated"] = updated
  90. if writeErr := filesys.WriteTree(tree); nil != writeErr {
  91. logging.LogErrorf("write tree [%s] failed: %s", tree.Path, writeErr)
  92. }
  93. }
  94. cache.PutDocIAL(file.path, docIAL)
  95. treenode.IndexBlockTree(tree)
  96. sql.IndexTreeQueue(box.ID, file.path)
  97. util.IncBootProgress(bootProgressPart, fmt.Sprintf(Conf.Language(92), util.ShortPathForBootingDisplay(tree.Path)))
  98. treeSize += file.size
  99. treeCount++
  100. if 1 < i && 0 == i%64 {
  101. util.PushStatusBar(fmt.Sprintf(Conf.Language(88), i, len(files)-i))
  102. }
  103. i++
  104. })
  105. for _, file := range files {
  106. if file.isdir || !strings.HasSuffix(file.name, ".sy") {
  107. continue
  108. }
  109. waitGroup.Add(1)
  110. p.Invoke(file)
  111. }
  112. waitGroup.Wait()
  113. p.Release()
  114. box.UpdateHistoryGenerated() // 初始化历史生成时间为当前时间
  115. end := time.Now()
  116. elapsed := end.Sub(start).Seconds()
  117. logging.LogInfof("rebuilt database for notebook [%s] in [%.2fs], tree [count=%d, size=%s]", box.ID, elapsed, treeCount, humanize.Bytes(uint64(treeSize)))
  118. debug.FreeOSMemory()
  119. return
  120. }
  121. func IndexRefs() {
  122. start := time.Now()
  123. util.SetBootDetails("Resolving refs...")
  124. util.PushStatusBar(Conf.Language(54))
  125. util.SetBootDetails("Indexing refs...")
  126. var defBlockIDs []string
  127. luteEngine := util.NewLute()
  128. boxes := Conf.GetOpenedBoxes()
  129. for _, box := range boxes {
  130. sql.DeleteBoxRefsQueue(box.ID)
  131. pages := pagedPaths(filepath.Join(util.DataDir, box.ID), 32)
  132. for _, paths := range pages {
  133. for _, treeAbsPath := range paths {
  134. data, readErr := filelock.ReadFile(treeAbsPath)
  135. if nil != readErr {
  136. logging.LogWarnf("get data [path=%s] failed: %s", treeAbsPath, readErr)
  137. continue
  138. }
  139. if !bytes.Contains(data, []byte("TextMarkBlockRefID")) && !bytes.Contains(data, []byte("TextMarkFileAnnotationRefID")) {
  140. continue
  141. }
  142. p := filepath.ToSlash(strings.TrimPrefix(treeAbsPath, filepath.Join(util.DataDir, box.ID)))
  143. tree, parseErr := filesys.LoadTreeByData(data, box.ID, p, luteEngine)
  144. if nil != parseErr {
  145. logging.LogWarnf("parse json to tree [%s] failed: %s", treeAbsPath, parseErr)
  146. continue
  147. }
  148. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  149. if !entering {
  150. return ast.WalkContinue
  151. }
  152. if treenode.IsBlockRef(n) || treenode.IsFileAnnotationRef(n) {
  153. defBlockIDs = append(defBlockIDs, tree.Root.ID)
  154. }
  155. return ast.WalkContinue
  156. })
  157. }
  158. }
  159. }
  160. defBlockIDs = gulu.Str.RemoveDuplicatedElem(defBlockIDs)
  161. i := 0
  162. size := len(defBlockIDs)
  163. if 0 < size {
  164. bootProgressPart := 10.0 / float64(size)
  165. for _, defBlockID := range defBlockIDs {
  166. defTree, loadErr := LoadTreeByID(defBlockID)
  167. if nil != loadErr {
  168. continue
  169. }
  170. util.IncBootProgress(bootProgressPart, "Indexing ref "+defTree.ID)
  171. sql.InsertRefsTreeQueue(defTree)
  172. if 1 < i && 0 == i%64 {
  173. util.PushStatusBar(fmt.Sprintf(Conf.Language(55), i))
  174. }
  175. i++
  176. }
  177. }
  178. logging.LogInfof("resolved refs [%d] in [%dms]", size, time.Now().Sub(start).Milliseconds())
  179. util.PushStatusBar(fmt.Sprintf(Conf.Language(55), i))
  180. }
  181. // IndexEmbedBlockJob 嵌入块支持搜索 https://github.com/siyuan-note/siyuan/issues/7112
  182. func IndexEmbedBlockJob() {
  183. embedBlocks := sql.QueryEmptyContentEmbedBlocks()
  184. task.AppendTaskWithTimeout(task.DatabaseIndexEmbedBlock, 30*time.Second, autoIndexEmbedBlock, embedBlocks)
  185. }
  186. func autoIndexEmbedBlock(embedBlocks []*sql.Block) {
  187. for i, embedBlock := range embedBlocks {
  188. stmt := strings.TrimPrefix(embedBlock.Markdown, "{{")
  189. stmt = strings.TrimSuffix(stmt, "}}")
  190. if !strings.Contains(strings.ToLower(stmt), "select") {
  191. continue
  192. }
  193. queryResultBlocks := sql.SelectBlocksRawStmtNoParse(stmt, 102400)
  194. for _, block := range queryResultBlocks {
  195. embedBlock.Content += block.Content
  196. }
  197. if "" == embedBlock.Content {
  198. embedBlock.Content = "no query result"
  199. }
  200. sql.UpdateBlockContentQueue(embedBlock)
  201. if 63 <= i { // 一次任务中最多处理 64 个嵌入块,防止卡顿
  202. break
  203. }
  204. }
  205. }
  206. func updateEmbedBlockContent(embedBlockID string, queryResultBlocks []*EmbedBlock) {
  207. embedBlock := sql.GetBlock(embedBlockID)
  208. if nil == embedBlock {
  209. return
  210. }
  211. embedBlock.Content = "" // 嵌入块每查询一次多一个结果 https://github.com/siyuan-note/siyuan/issues/7196
  212. for _, block := range queryResultBlocks {
  213. embedBlock.Content += block.Block.Markdown
  214. }
  215. if "" == embedBlock.Content {
  216. embedBlock.Content = "no query result"
  217. }
  218. sql.UpdateBlockContentQueue(embedBlock)
  219. }
  220. func init() {
  221. subscribeSQLEvents()
  222. }
  223. func subscribeSQLEvents() {
  224. //eventbus.Subscribe(eventbus.EvtSQLInsertBlocks, func(context map[string]interface{}, current, total, blockCount int, hash string) {
  225. // if util.ContainerAndroid == util.Container || util.ContainerIOS == util.Container {
  226. // // Android/iOS 端不显示数据索引和搜索索引状态提示 https://github.com/siyuan-note/siyuan/issues/6392
  227. // return
  228. // }
  229. //
  230. // msg := fmt.Sprintf(Conf.Language(89), current, total, blockCount, hash)
  231. // util.SetBootDetails(msg)
  232. // util.ContextPushMsg(context, msg)
  233. //})
  234. eventbus.Subscribe(eventbus.EvtSQLInsertBlocksFTS, func(context map[string]interface{}, blockCount int, hash string) {
  235. if util.ContainerAndroid == util.Container || util.ContainerIOS == util.Container {
  236. // Android/iOS 端不显示数据索引和搜索索引状态提示 https://github.com/siyuan-note/siyuan/issues/6392
  237. return
  238. }
  239. current := context["current"].(int)
  240. total := context["total"]
  241. msg := fmt.Sprintf(Conf.Language(90), current, total, blockCount, hash)
  242. util.SetBootDetails(msg)
  243. util.ContextPushMsg(context, msg)
  244. })
  245. eventbus.Subscribe(eventbus.EvtSQLDeleteBlocks, func(context map[string]interface{}, rootID string) {
  246. if util.ContainerAndroid == util.Container || util.ContainerIOS == util.Container {
  247. // Android/iOS 端不显示数据索引和搜索索引状态提示 https://github.com/siyuan-note/siyuan/issues/6392
  248. return
  249. }
  250. current := context["current"].(int)
  251. total := context["total"]
  252. msg := fmt.Sprintf(Conf.Language(93), current, total, rootID)
  253. util.SetBootDetails(msg)
  254. util.ContextPushMsg(context, msg)
  255. })
  256. eventbus.Subscribe(eventbus.EvtSQLInsertHistory, func(context map[string]interface{}) {
  257. if util.ContainerAndroid == util.Container || util.ContainerIOS == util.Container {
  258. return
  259. }
  260. current := context["current"].(int)
  261. total := context["total"]
  262. msg := fmt.Sprintf(Conf.Language(191), current, total)
  263. util.SetBootDetails(msg)
  264. util.ContextPushMsg(context, msg)
  265. })
  266. eventbus.Subscribe(eventbus.EvtSQLInsertAssetContent, func(context map[string]interface{}) {
  267. if util.ContainerAndroid == util.Container || util.ContainerIOS == util.Container {
  268. return
  269. }
  270. current := context["current"].(int)
  271. total := context["total"]
  272. msg := fmt.Sprintf(Conf.Language(217), current, total)
  273. util.SetBootDetails(msg)
  274. util.ContextPushMsg(context, msg)
  275. })
  276. }