file.go 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723
  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. "errors"
  19. "fmt"
  20. "os"
  21. "path"
  22. "path/filepath"
  23. "sort"
  24. "strconv"
  25. "strings"
  26. "time"
  27. "unicode/utf8"
  28. "github.com/88250/gulu"
  29. "github.com/88250/lute"
  30. "github.com/88250/lute/ast"
  31. "github.com/88250/lute/html"
  32. "github.com/88250/lute/parse"
  33. util2 "github.com/88250/lute/util"
  34. "github.com/dustin/go-humanize"
  35. "github.com/facette/natsort"
  36. "github.com/siyuan-note/filelock"
  37. "github.com/siyuan-note/logging"
  38. "github.com/siyuan-note/riff"
  39. "github.com/siyuan-note/siyuan/kernel/cache"
  40. "github.com/siyuan-note/siyuan/kernel/filesys"
  41. "github.com/siyuan-note/siyuan/kernel/search"
  42. "github.com/siyuan-note/siyuan/kernel/sql"
  43. "github.com/siyuan-note/siyuan/kernel/task"
  44. "github.com/siyuan-note/siyuan/kernel/treenode"
  45. "github.com/siyuan-note/siyuan/kernel/util"
  46. )
  47. type File struct {
  48. Path string `json:"path"`
  49. Name string `json:"name"` // 标题,即 ial["title"]
  50. Icon string `json:"icon"`
  51. Name1 string `json:"name1"` // 命名,即 ial["name"]
  52. Alias string `json:"alias"`
  53. Memo string `json:"memo"`
  54. Bookmark string `json:"bookmark"`
  55. ID string `json:"id"`
  56. Count int `json:"count"`
  57. Size uint64 `json:"size"`
  58. HSize string `json:"hSize"`
  59. Mtime int64 `json:"mtime"`
  60. CTime int64 `json:"ctime"`
  61. HMtime string `json:"hMtime"`
  62. HCtime string `json:"hCtime"`
  63. Sort int `json:"sort"`
  64. SubFileCount int `json:"subFileCount"`
  65. NewFlashcardCount int `json:"newFlashcardCount"`
  66. DueFlashcardCount int `json:"dueFlashcardCount"`
  67. FlashcardCount int `json:"flashcardCount"`
  68. }
  69. func (box *Box) docFromFileInfo(fileInfo *FileInfo, ial map[string]string) (ret *File) {
  70. ret = &File{}
  71. ret.Path = fileInfo.path
  72. ret.Size = uint64(fileInfo.size)
  73. ret.Name = ial["title"] + ".sy"
  74. ret.Icon = ial["icon"]
  75. ret.ID = ial["id"]
  76. ret.Name1 = ial["name"]
  77. ret.Alias = ial["alias"]
  78. ret.Memo = ial["memo"]
  79. ret.Bookmark = ial["bookmark"]
  80. t, _ := time.ParseInLocation("20060102150405", ret.ID[:14], time.Local)
  81. ret.CTime = t.Unix()
  82. ret.HCtime = t.Format("2006-01-02 15:04:05")
  83. ret.HSize = humanize.Bytes(ret.Size)
  84. mTime := t
  85. if updated := ial["updated"]; "" != updated {
  86. if updatedTime, err := time.ParseInLocation("20060102150405", updated, time.Local); nil == err {
  87. mTime = updatedTime
  88. }
  89. }
  90. ret.Mtime = mTime.Unix()
  91. ret.HMtime = util.HumanizeTime(mTime, Conf.Lang)
  92. return
  93. }
  94. func (box *Box) docIAL(p string) (ret map[string]string) {
  95. name := strings.ToLower(filepath.Base(p))
  96. if !strings.HasSuffix(name, ".sy") {
  97. return nil
  98. }
  99. ret = cache.GetDocIAL(p)
  100. if nil != ret {
  101. return ret
  102. }
  103. filePath := filepath.Join(util.DataDir, box.ID, p)
  104. data, err := filelock.ReadFile(filePath)
  105. if util.IsCorruptedSYData(data) {
  106. box.moveCorruptedData(filePath)
  107. return nil
  108. }
  109. if nil != err {
  110. logging.LogErrorf("read file [%s] failed: %s", p, err)
  111. return nil
  112. }
  113. ret = filesys.ReadDocIAL(data)
  114. if 1 > len(ret) {
  115. logging.LogWarnf("tree [%s] is corrupted", filePath)
  116. box.moveCorruptedData(filePath)
  117. return nil
  118. }
  119. cache.PutDocIAL(p, ret)
  120. return ret
  121. }
  122. func (box *Box) moveCorruptedData(filePath string) {
  123. base := filepath.Base(filePath)
  124. to := filepath.Join(util.WorkspaceDir, "corrupted", time.Now().Format("2006-01-02-150405"), box.ID, base)
  125. if copyErr := filelock.Copy(filePath, to); nil != copyErr {
  126. logging.LogErrorf("copy corrupted data file [%s] failed: %s", filePath, copyErr)
  127. return
  128. }
  129. if removeErr := filelock.Remove(filePath); nil != removeErr {
  130. logging.LogErrorf("remove corrupted data file [%s] failed: %s", filePath, removeErr)
  131. return
  132. }
  133. logging.LogWarnf("moved corrupted data file [%s] to [%s]", filePath, to)
  134. }
  135. func SearchDocsByKeyword(keyword string, flashcard bool) (ret []map[string]string) {
  136. ret = []map[string]string{}
  137. var deck *riff.Deck
  138. var deckBlockIDs []string
  139. if flashcard {
  140. deck = Decks[builtinDeckID]
  141. if nil == deck {
  142. return
  143. }
  144. deckBlockIDs = deck.GetBlockIDs()
  145. }
  146. openedBoxes := Conf.GetOpenedBoxes()
  147. boxes := map[string]*Box{}
  148. for _, box := range openedBoxes {
  149. boxes[box.ID] = box
  150. }
  151. var rootBlocks []*sql.Block
  152. if "" != keyword {
  153. for _, box := range boxes {
  154. if strings.Contains(box.Name, keyword) {
  155. if flashcard {
  156. newFlashcardCount, dueFlashcardCount, flashcardCount := countBoxFlashcard(box.ID, deck, deckBlockIDs)
  157. if 0 < flashcardCount {
  158. ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon, "newFlashcardCount": strconv.Itoa(newFlashcardCount), "dueFlashcardCount": strconv.Itoa(dueFlashcardCount), "flashcardCount": strconv.Itoa(flashcardCount)})
  159. }
  160. } else {
  161. ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon})
  162. }
  163. }
  164. }
  165. condition := "hpath LIKE '%" + keyword + "%'"
  166. if "" != keyword {
  167. namCondition := Conf.Search.NAMFilter(keyword)
  168. if "" != namCondition {
  169. condition += " " + namCondition
  170. }
  171. }
  172. rootBlocks = sql.QueryRootBlockByCondition(condition)
  173. } else {
  174. for _, box := range boxes {
  175. if flashcard {
  176. newFlashcardCount, dueFlashcardCount, flashcardCount := countBoxFlashcard(box.ID, deck, deckBlockIDs)
  177. if 0 < flashcardCount {
  178. ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon, "newFlashcardCount": strconv.Itoa(newFlashcardCount), "dueFlashcardCount": strconv.Itoa(dueFlashcardCount), "flashcardCount": strconv.Itoa(flashcardCount)})
  179. }
  180. } else {
  181. ret = append(ret, map[string]string{"path": "/", "hPath": box.Name + "/", "box": box.ID, "boxIcon": box.Icon})
  182. }
  183. }
  184. }
  185. for _, rootBlock := range rootBlocks {
  186. b := boxes[rootBlock.Box]
  187. if nil == b {
  188. continue
  189. }
  190. hPath := b.Name + rootBlock.HPath
  191. if flashcard {
  192. newFlashcardCount, dueFlashcardCount, flashcardCount := countTreeFlashcard(rootBlock.ID, deck, deckBlockIDs)
  193. if 0 < flashcardCount {
  194. ret = append(ret, map[string]string{"path": rootBlock.Path, "hPath": hPath, "box": rootBlock.Box, "boxIcon": b.Icon, "newFlashcardCount": strconv.Itoa(newFlashcardCount), "dueFlashcardCount": strconv.Itoa(dueFlashcardCount), "flashcardCount": strconv.Itoa(flashcardCount)})
  195. }
  196. } else {
  197. ret = append(ret, map[string]string{"path": rootBlock.Path, "hPath": hPath, "box": rootBlock.Box, "boxIcon": b.Icon})
  198. }
  199. }
  200. sort.Slice(ret, func(i, j int) bool {
  201. return ret[i]["hPath"] < ret[j]["hPath"]
  202. })
  203. return
  204. }
  205. type FileInfo struct {
  206. path string
  207. name string
  208. size int64
  209. isdir bool
  210. }
  211. func ListDocTree(boxID, path string, sortMode int, flashcard bool, maxListCount int) (ret []*File, totals int, err error) {
  212. //os.MkdirAll("pprof", 0755)
  213. //cpuProfile, _ := os.Create("pprof/cpu_profile_list_doc_tree")
  214. //pprof.StartCPUProfile(cpuProfile)
  215. //defer pprof.StopCPUProfile()
  216. ret = []*File{}
  217. var deck *riff.Deck
  218. var deckBlockIDs []string
  219. if flashcard {
  220. deck = Decks[builtinDeckID]
  221. if nil == deck {
  222. return
  223. }
  224. deckBlockIDs = deck.GetBlockIDs()
  225. }
  226. box := Conf.Box(boxID)
  227. if nil == box {
  228. return nil, 0, errors.New(Conf.Language(0))
  229. }
  230. boxConf := box.GetConf()
  231. if util.SortModeUnassigned == sortMode {
  232. sortMode = Conf.FileTree.Sort
  233. if util.SortModeFileTree != boxConf.SortMode {
  234. sortMode = boxConf.SortMode
  235. }
  236. }
  237. var files []*FileInfo
  238. start := time.Now()
  239. files, totals, err = box.Ls(path)
  240. if nil != err {
  241. return
  242. }
  243. elapsed := time.Now().Sub(start).Milliseconds()
  244. if 100 < elapsed {
  245. logging.LogWarnf("ls elapsed [%dms]", elapsed)
  246. }
  247. start = time.Now()
  248. boxLocalPath := filepath.Join(util.DataDir, box.ID)
  249. var docs []*File
  250. for _, file := range files {
  251. if file.isdir {
  252. if !ast.IsNodeIDPattern(file.name) {
  253. continue
  254. }
  255. parentDocPath := strings.TrimSuffix(file.path, "/") + ".sy"
  256. subDocFile := box.Stat(parentDocPath)
  257. if nil == subDocFile {
  258. continue
  259. }
  260. if ial := box.docIAL(parentDocPath); nil != ial {
  261. doc := box.docFromFileInfo(subDocFile, ial)
  262. subFiles, err := os.ReadDir(filepath.Join(boxLocalPath, file.path))
  263. if nil == err {
  264. for _, subFile := range subFiles {
  265. if strings.HasSuffix(subFile.Name(), ".sy") {
  266. doc.SubFileCount++
  267. }
  268. }
  269. }
  270. if flashcard {
  271. rootID := strings.TrimSuffix(filepath.Base(parentDocPath), ".sy")
  272. newFlashcardCount, dueFlashcardCount, flashcardCount := countTreeFlashcard(rootID, deck, deckBlockIDs)
  273. if 0 < flashcardCount {
  274. doc.NewFlashcardCount = newFlashcardCount
  275. doc.DueFlashcardCount = dueFlashcardCount
  276. doc.FlashcardCount = flashcardCount
  277. docs = append(docs, doc)
  278. }
  279. } else {
  280. docs = append(docs, doc)
  281. }
  282. }
  283. continue
  284. }
  285. subFolder := filepath.Join(boxLocalPath, strings.TrimSuffix(file.path, ".sy"))
  286. if gulu.File.IsDir(subFolder) {
  287. continue
  288. }
  289. if ial := box.docIAL(file.path); nil != ial {
  290. doc := box.docFromFileInfo(file, ial)
  291. if flashcard {
  292. rootID := strings.TrimSuffix(filepath.Base(file.path), ".sy")
  293. newFlashcardCount, dueFlashcardCount, flashcardCount := countTreeFlashcard(rootID, deck, deckBlockIDs)
  294. if 0 < flashcardCount {
  295. doc.NewFlashcardCount = newFlashcardCount
  296. doc.DueFlashcardCount = dueFlashcardCount
  297. doc.FlashcardCount = flashcardCount
  298. docs = append(docs, doc)
  299. }
  300. } else {
  301. docs = append(docs, doc)
  302. }
  303. }
  304. }
  305. elapsed = time.Now().Sub(start).Milliseconds()
  306. if 500 < elapsed {
  307. logging.LogWarnf("build docs [%d] elapsed [%dms]", len(docs), elapsed)
  308. }
  309. start = time.Now()
  310. refCount := sql.QueryRootBlockRefCount()
  311. for _, doc := range docs {
  312. if count := refCount[doc.ID]; 0 < count {
  313. doc.Count = count
  314. }
  315. }
  316. elapsed = time.Now().Sub(start).Milliseconds()
  317. if 500 < elapsed {
  318. logging.LogWarnf("query root block ref count elapsed [%dms]", elapsed)
  319. }
  320. start = time.Now()
  321. switch sortMode {
  322. case util.SortModeNameASC:
  323. sort.Slice(docs, func(i, j int) bool {
  324. return util.PinYinCompare(util.RemoveEmoji(docs[i].Name), util.RemoveEmoji(docs[j].Name))
  325. })
  326. case util.SortModeNameDESC:
  327. sort.Slice(docs, func(i, j int) bool {
  328. return util.PinYinCompare(util.RemoveEmoji(docs[j].Name), util.RemoveEmoji(docs[i].Name))
  329. })
  330. case util.SortModeUpdatedASC:
  331. sort.Slice(docs, func(i, j int) bool { return docs[i].Mtime < docs[j].Mtime })
  332. case util.SortModeUpdatedDESC:
  333. sort.Slice(docs, func(i, j int) bool { return docs[i].Mtime > docs[j].Mtime })
  334. case util.SortModeAlphanumASC:
  335. sort.Slice(docs, func(i, j int) bool {
  336. return natsort.Compare(util.RemoveEmoji(docs[i].Name), util.RemoveEmoji(docs[j].Name))
  337. })
  338. case util.SortModeAlphanumDESC:
  339. sort.Slice(docs, func(i, j int) bool {
  340. return natsort.Compare(util.RemoveEmoji(docs[j].Name), util.RemoveEmoji(docs[i].Name))
  341. })
  342. case util.SortModeCustom:
  343. fileTreeFiles := docs
  344. box.fillSort(&fileTreeFiles)
  345. sort.Slice(fileTreeFiles, func(i, j int) bool {
  346. if fileTreeFiles[i].Sort == fileTreeFiles[j].Sort {
  347. return util.TimeFromID(fileTreeFiles[i].ID) > util.TimeFromID(fileTreeFiles[j].ID)
  348. }
  349. return fileTreeFiles[i].Sort < fileTreeFiles[j].Sort
  350. })
  351. ret = append(ret, fileTreeFiles...)
  352. totals = len(ret)
  353. if maxListCount < len(ret) {
  354. ret = ret[:maxListCount]
  355. }
  356. ret = ret[:]
  357. return
  358. case util.SortModeRefCountASC:
  359. sort.Slice(docs, func(i, j int) bool { return docs[i].Count < docs[j].Count })
  360. case util.SortModeRefCountDESC:
  361. sort.Slice(docs, func(i, j int) bool { return docs[i].Count > docs[j].Count })
  362. case util.SortModeCreatedASC:
  363. sort.Slice(docs, func(i, j int) bool { return docs[i].CTime < docs[j].CTime })
  364. case util.SortModeCreatedDESC:
  365. sort.Slice(docs, func(i, j int) bool { return docs[i].CTime > docs[j].CTime })
  366. case util.SortModeSizeASC:
  367. sort.Slice(docs, func(i, j int) bool { return docs[i].Size < docs[j].Size })
  368. case util.SortModeSizeDESC:
  369. sort.Slice(docs, func(i, j int) bool { return docs[i].Size > docs[j].Size })
  370. case util.SortModeSubDocCountASC:
  371. sort.Slice(docs, func(i, j int) bool { return docs[i].SubFileCount < docs[j].SubFileCount })
  372. case util.SortModeSubDocCountDESC:
  373. sort.Slice(docs, func(i, j int) bool { return docs[i].SubFileCount > docs[j].SubFileCount })
  374. }
  375. if util.SortModeCustom != sortMode {
  376. ret = append(ret, docs...)
  377. }
  378. totals = len(ret)
  379. if maxListCount < len(ret) {
  380. ret = ret[:maxListCount]
  381. }
  382. ret = ret[:]
  383. elapsed = time.Now().Sub(start).Milliseconds()
  384. if 200 < elapsed {
  385. logging.LogInfof("sort docs elapsed [%dms]", elapsed)
  386. }
  387. return
  388. }
  389. func ContentStat(content string) (ret *util.BlockStatResult) {
  390. luteEngine := util.NewLute()
  391. tree := luteEngine.BlockDOM2Tree(content)
  392. runeCnt, wordCnt, linkCnt, imgCnt, refCnt := tree.Root.Stat()
  393. return &util.BlockStatResult{
  394. RuneCount: runeCnt,
  395. WordCount: wordCnt,
  396. LinkCount: linkCnt,
  397. ImageCount: imgCnt,
  398. RefCount: refCnt,
  399. }
  400. }
  401. func BlocksWordCount(ids []string) (ret *util.BlockStatResult) {
  402. ret = &util.BlockStatResult{}
  403. trees := map[string]*parse.Tree{} // 缓存
  404. luteEngine := util.NewLute()
  405. for _, id := range ids {
  406. bt := treenode.GetBlockTree(id)
  407. if nil == bt {
  408. logging.LogWarnf("block tree not found [%s]", id)
  409. continue
  410. }
  411. tree := trees[bt.RootID]
  412. if nil == tree {
  413. tree, _ = filesys.LoadTree(bt.BoxID, bt.Path, luteEngine)
  414. if nil == tree {
  415. continue
  416. }
  417. trees[bt.RootID] = tree
  418. }
  419. node := treenode.GetNodeInTree(tree, id)
  420. runeCnt, wordCnt, linkCnt, imgCnt, refCnt := node.Stat()
  421. ret.RuneCount += runeCnt
  422. ret.WordCount += wordCnt
  423. ret.LinkCount += linkCnt
  424. ret.ImageCount += imgCnt
  425. ret.RefCount += refCnt
  426. }
  427. return
  428. }
  429. func StatTree(id string) (ret *util.BlockStatResult) {
  430. WaitForWritingFiles()
  431. tree, _ := loadTreeByBlockID(id)
  432. if nil == tree {
  433. return
  434. }
  435. runeCnt, wordCnt, linkCnt, imgCnt, refCnt := tree.Root.Stat()
  436. return &util.BlockStatResult{
  437. RuneCount: runeCnt,
  438. WordCount: wordCnt,
  439. LinkCount: linkCnt,
  440. ImageCount: imgCnt,
  441. RefCount: refCnt,
  442. }
  443. }
  444. func GetDoc(startID, endID, id string, index int, query string, queryTypes map[string]bool, queryMethod, mode int, size int, isBacklink bool) (blockCount int, dom, parentID, parent2ID, rootID, typ string, eof, scroll bool, boxID, docPath string, isBacklinkExpand bool, err error) {
  445. //os.MkdirAll("pprof", 0755)
  446. //cpuProfile, _ := os.Create("pprof/GetDoc")
  447. //pprof.StartCPUProfile(cpuProfile)
  448. //defer pprof.StopCPUProfile()
  449. WaitForWritingFiles() // 写入数据时阻塞,避免获取到的数据不一致
  450. inputIndex := index
  451. tree, err := loadTreeByBlockID(id)
  452. if nil != err {
  453. if ErrBlockNotFound == err {
  454. if 0 == mode {
  455. err = ErrTreeNotFound // 初始化打开文档时如果找不到则关闭编辑器
  456. }
  457. }
  458. return
  459. }
  460. if nil == tree {
  461. err = ErrBlockNotFound
  462. return
  463. }
  464. luteEngine := NewLute()
  465. node := treenode.GetNodeInTree(tree, id)
  466. if nil == node {
  467. err = ErrBlockNotFound
  468. return
  469. }
  470. if isBacklink { // 引用计数浮窗请求,需要按照反链逻辑组装 https://github.com/siyuan-note/siyuan/issues/6853
  471. if ast.NodeParagraph == node.Type {
  472. if nil != node.Parent && ast.NodeListItem == node.Parent.Type {
  473. node = node.Parent
  474. }
  475. }
  476. }
  477. located := false
  478. isDoc := ast.NodeDocument == node.Type
  479. isHeading := ast.NodeHeading == node.Type
  480. boxID = node.Box
  481. docPath = node.Path
  482. if isDoc {
  483. if 4 == mode { // 加载文档末尾
  484. node = node.LastChild
  485. located = true
  486. // 重新计算 index
  487. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  488. if !entering {
  489. return ast.WalkContinue
  490. }
  491. index++
  492. return ast.WalkContinue
  493. })
  494. } else {
  495. node = node.FirstChild
  496. }
  497. typ = ast.NodeDocument.String()
  498. idx := 0
  499. if 0 < index {
  500. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  501. if !entering || !n.IsChildBlockOf(tree.Root, 1) {
  502. return ast.WalkContinue
  503. }
  504. idx++
  505. if index == idx {
  506. node = n.DocChild()
  507. if "1" == node.IALAttr("heading-fold") {
  508. // 加载到折叠标题下方块的话需要回溯到上方标题块
  509. for h := node.Previous; nil != h; h = h.Previous {
  510. if "1" == h.IALAttr("fold") {
  511. node = h
  512. break
  513. }
  514. }
  515. }
  516. located = true
  517. return ast.WalkStop
  518. }
  519. return ast.WalkContinue
  520. })
  521. }
  522. } else {
  523. if 0 == index && 0 != mode {
  524. // 非文档且没有指定 index 时需要计算 index
  525. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  526. if !entering {
  527. return ast.WalkContinue
  528. }
  529. index++
  530. if id == n.ID {
  531. node = n.DocChild()
  532. located = true
  533. return ast.WalkStop
  534. }
  535. return ast.WalkContinue
  536. })
  537. }
  538. }
  539. if 1 < index && !located {
  540. count := 0
  541. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  542. if !entering {
  543. return ast.WalkContinue
  544. }
  545. count++
  546. if index == count {
  547. node = n.DocChild()
  548. return ast.WalkStop
  549. }
  550. return ast.WalkContinue
  551. })
  552. }
  553. blockCount = tree.DocBlockCount()
  554. if ast.NodeDocument == node.Type {
  555. parentID = node.ID
  556. parent2ID = parentID
  557. } else {
  558. parentID = node.Parent.ID
  559. parent2ID = parentID
  560. tmp := node
  561. if ast.NodeListItem == node.Type {
  562. // 列表项聚焦返回和面包屑保持一致 https://github.com/siyuan-note/siyuan/issues/4914
  563. tmp = node.Parent
  564. }
  565. if headingParent := treenode.HeadingParent(tmp); nil != headingParent {
  566. parent2ID = headingParent.ID
  567. }
  568. }
  569. rootID = tree.Root.ID
  570. if !isDoc {
  571. typ = node.Type.String()
  572. }
  573. // 判断是否需要显示动态加载滚动条 https://github.com/siyuan-note/siyuan/issues/7693
  574. childCount := 0
  575. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  576. if !entering {
  577. return ast.WalkContinue
  578. }
  579. if 1 > childCount {
  580. childCount = 1
  581. } else {
  582. childCount += treenode.CountBlockNodes(n)
  583. }
  584. if childCount > Conf.Editor.DynamicLoadBlocks {
  585. scroll = true
  586. return ast.WalkStop
  587. }
  588. return ast.WalkContinue
  589. })
  590. var nodes []*ast.Node
  591. if isBacklink {
  592. // 引用计数浮窗请求,需要按照反链逻辑组装 https://github.com/siyuan-note/siyuan/issues/6853
  593. nodes, isBacklinkExpand = getBacklinkRenderNodes(node)
  594. } else {
  595. // 如果同时存在 startID 和 endID,并且是动态加载的情况,则只加载 startID 和 endID 之间的块 [startID, endID]
  596. if "" != startID && "" != endID && scroll {
  597. nodes, eof = loadNodesByStartEnd(tree, startID, endID)
  598. if 1 > len(nodes) {
  599. // 按 mode 加载兜底
  600. nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
  601. }
  602. } else {
  603. nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
  604. }
  605. }
  606. refCount := sql.QueryRootChildrenRefCount(rootID)
  607. virtualBlockRefKeywords := getBlockVirtualRefKeywords(tree.Root)
  608. subTree := &parse.Tree{ID: rootID, Root: &ast.Node{Type: ast.NodeDocument}, Marks: tree.Marks}
  609. var keywords []string
  610. if "" != query && (0 == queryMethod || 1 == queryMethod) { // 只有关键字搜索和查询语法搜索才支持高亮
  611. if 0 == queryMethod {
  612. query = stringQuery(query)
  613. }
  614. typeFilter := buildTypeFilter(queryTypes)
  615. keywords = highlightByQuery(query, typeFilter, rootID)
  616. }
  617. for _, n := range nodes {
  618. var unlinks []*ast.Node
  619. ast.Walk(n, func(n *ast.Node, entering bool) ast.WalkStatus {
  620. if !entering {
  621. return ast.WalkContinue
  622. }
  623. if "1" == n.IALAttr("heading-fold") {
  624. unlinks = append(unlinks, n)
  625. return ast.WalkContinue
  626. }
  627. if "" != n.ID {
  628. // 填充块引计数
  629. if cnt := refCount[n.ID]; 0 < cnt {
  630. n.SetIALAttr("refcount", strconv.Itoa(cnt))
  631. }
  632. }
  633. if 0 < len(keywords) {
  634. hitBlock := false
  635. for p := n.Parent; nil != p; p = p.Parent {
  636. if p.ID == id {
  637. hitBlock = true
  638. break
  639. }
  640. }
  641. if hitBlock {
  642. if ast.NodeCodeBlockCode == n.Type && !treenode.IsChartCodeBlockCode(n) {
  643. // 支持代码块搜索定位 https://github.com/siyuan-note/siyuan/issues/5520
  644. code := string(n.Tokens)
  645. markedCode := search.EncloseHighlighting(code, keywords, search.SearchMarkLeft, search.SearchMarkRight, Conf.Search.CaseSensitive, false)
  646. if code != markedCode {
  647. n.Tokens = gulu.Str.ToBytes(markedCode)
  648. return ast.WalkContinue
  649. }
  650. } else if markReplaceSpan(n, &unlinks, keywords, search.MarkDataType, luteEngine) {
  651. return ast.WalkContinue
  652. }
  653. }
  654. }
  655. if processVirtualRef(n, &unlinks, virtualBlockRefKeywords, refCount, luteEngine) {
  656. return ast.WalkContinue
  657. }
  658. return ast.WalkContinue
  659. })
  660. for _, unlink := range unlinks {
  661. unlink.Unlink()
  662. }
  663. subTree.Root.AppendChild(n)
  664. }
  665. luteEngine.RenderOptions.NodeIndexStart = index
  666. dom = luteEngine.Tree2BlockDOM(subTree, luteEngine.RenderOptions)
  667. SetRecentDocByTree(tree)
  668. return
  669. }
  670. func loadNodesByStartEnd(tree *parse.Tree, startID, endID string) (nodes []*ast.Node, eof bool) {
  671. node := treenode.GetNodeInTree(tree, startID)
  672. if nil == node {
  673. return
  674. }
  675. nodes = append(nodes, node)
  676. for n := node.Next; nil != n; n = n.Next {
  677. if treenode.IsInFoldedHeading(n, nil) {
  678. continue
  679. }
  680. nodes = append(nodes, n)
  681. if n.ID == endID {
  682. if next := n.Next; nil == next {
  683. eof = true
  684. } else {
  685. eof = util2.IsDocIAL(n.Tokens) || util2.IsDocIAL(next.Tokens)
  686. }
  687. break
  688. }
  689. }
  690. return
  691. }
  692. func loadNodesByMode(node *ast.Node, inputIndex, mode, size int, isDoc, isHeading bool) (nodes []*ast.Node, eof bool) {
  693. if 2 == mode /* 向下 */ {
  694. next := node.Next
  695. if ast.NodeHeading == node.Type && "1" == node.IALAttr("fold") {
  696. // 标题展开时进行动态加载导致重复内容 https://github.com/siyuan-note/siyuan/issues/4671
  697. // 这里要考虑折叠标题是最后一个块的情况
  698. if children := treenode.HeadingChildren(node); 0 < len(children) {
  699. next = children[len(children)-1].Next
  700. }
  701. }
  702. if nil == next {
  703. eof = true
  704. } else {
  705. eof = util2.IsDocIAL(node.Tokens) || util2.IsDocIAL(next.Tokens)
  706. }
  707. }
  708. count := 0
  709. switch mode {
  710. case 0: // 仅加载当前 ID
  711. nodes = append(nodes, node)
  712. if isDoc {
  713. for n := node.Next; nil != n; n = n.Next {
  714. if treenode.IsInFoldedHeading(n, nil) {
  715. continue
  716. }
  717. nodes = append(nodes, n)
  718. if 1 > count {
  719. count++
  720. } else {
  721. count += treenode.CountBlockNodes(n)
  722. }
  723. if size < count {
  724. break
  725. }
  726. }
  727. } else if isHeading {
  728. level := node.HeadingLevel
  729. for n := node.Next; nil != n; n = n.Next {
  730. if treenode.IsInFoldedHeading(n, node) {
  731. // 大纲点击折叠标题跳转聚焦 https://github.com/siyuan-note/siyuan/issues/4920
  732. // 多级标题折叠后上级块引浮窗中未折叠 https://github.com/siyuan-note/siyuan/issues/4997
  733. continue
  734. }
  735. if ast.NodeHeading == n.Type {
  736. if n.HeadingLevel <= level {
  737. break
  738. }
  739. } else if ast.NodeSuperBlock == n.Type {
  740. if h := treenode.SuperBlockHeading(n); nil != h {
  741. if level >= h.HeadingLevel {
  742. break
  743. }
  744. }
  745. }
  746. nodes = append(nodes, n)
  747. count++
  748. if size < count {
  749. break
  750. }
  751. }
  752. }
  753. case 4: // Ctrl+End 跳转到末尾后向上加载
  754. for n := node; nil != n; n = n.Previous {
  755. if treenode.IsInFoldedHeading(n, nil) {
  756. continue
  757. }
  758. nodes = append([]*ast.Node{n}, nodes...)
  759. if 1 > count {
  760. count++
  761. } else {
  762. count += treenode.CountBlockNodes(n)
  763. }
  764. if size < count {
  765. break
  766. }
  767. }
  768. eof = true
  769. case 1: // 向上加载
  770. for n := node.Previous; /* 从上一个节点开始加载 */ nil != n; n = n.Previous {
  771. if treenode.IsInFoldedHeading(n, nil) {
  772. continue
  773. }
  774. nodes = append([]*ast.Node{n}, nodes...)
  775. if 1 > count {
  776. count++
  777. } else {
  778. count += treenode.CountBlockNodes(n)
  779. }
  780. if size < count {
  781. break
  782. }
  783. }
  784. eof = nil == node.Previous
  785. case 2: // 向下加载
  786. for n := node.Next; /* 从下一个节点开始加载 */ nil != n; n = n.Next {
  787. if treenode.IsInFoldedHeading(n, node) {
  788. continue
  789. }
  790. nodes = append(nodes, n)
  791. if 1 > count {
  792. count++
  793. } else {
  794. count += treenode.CountBlockNodes(n)
  795. }
  796. if size < count {
  797. break
  798. }
  799. }
  800. case 3: // 上下都加载
  801. for n := node; nil != n; n = n.Previous {
  802. if treenode.IsInFoldedHeading(n, nil) {
  803. continue
  804. }
  805. nodes = append([]*ast.Node{n}, nodes...)
  806. if 1 > count {
  807. count++
  808. } else {
  809. count += treenode.CountBlockNodes(n)
  810. }
  811. if 0 < inputIndex {
  812. if 1 < count {
  813. break // 滑块指示器加载
  814. }
  815. } else {
  816. if size < count {
  817. break
  818. }
  819. }
  820. }
  821. if size/2 < count {
  822. size = size / 2
  823. } else {
  824. size = size - count
  825. }
  826. count = 0
  827. for n := node.Next; nil != n; n = n.Next {
  828. if treenode.IsInFoldedHeading(n, nil) {
  829. continue
  830. }
  831. nodes = append(nodes, n)
  832. if 1 > count {
  833. count++
  834. } else {
  835. count += treenode.CountBlockNodes(n)
  836. }
  837. if 0 < inputIndex {
  838. if size < count {
  839. break
  840. }
  841. } else {
  842. if size < count {
  843. break
  844. }
  845. }
  846. }
  847. }
  848. return
  849. }
  850. func writeJSONQueue(tree *parse.Tree) (err error) {
  851. if err = filesys.WriteTree(tree); nil != err {
  852. return
  853. }
  854. sql.UpsertTreeQueue(tree)
  855. return
  856. }
  857. func writeJSONQueueWithoutChangeTime(tree *parse.Tree) (err error) {
  858. if err = filesys.WriteTreeWithoutChangeTime(tree); nil != err {
  859. return
  860. }
  861. sql.UpsertTreeQueue(tree)
  862. return
  863. }
  864. func indexWriteJSONQueue(tree *parse.Tree) (err error) {
  865. treenode.IndexBlockTree(tree)
  866. return writeJSONQueue(tree)
  867. }
  868. func indexWriteJSONQueueWithoutChangeTime(tree *parse.Tree) (err error) {
  869. treenode.IndexBlockTree(tree)
  870. return writeJSONQueueWithoutChangeTime(tree)
  871. }
  872. func renameWriteJSONQueue(tree *parse.Tree) (err error) {
  873. if err = filesys.WriteTree(tree); nil != err {
  874. return
  875. }
  876. sql.RenameTreeQueue(tree)
  877. treenode.IndexBlockTree(tree)
  878. return
  879. }
  880. func DuplicateDoc(tree *parse.Tree) {
  881. msgId := util.PushMsg(Conf.Language(116), 30000)
  882. defer util.PushClearMsg(msgId)
  883. resetTree(tree, "Duplicated")
  884. createTreeTx(tree)
  885. WaitForWritingFiles()
  886. return
  887. }
  888. func createTreeTx(tree *parse.Tree) {
  889. transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
  890. PerformTransactions(&[]*Transaction{transaction})
  891. }
  892. func CreateDocByMd(boxID, p, title, md string, sorts []string) (tree *parse.Tree, err error) {
  893. box := Conf.Box(boxID)
  894. if nil == box {
  895. err = errors.New(Conf.Language(0))
  896. return
  897. }
  898. luteEngine := util.NewLute()
  899. dom := luteEngine.Md2BlockDOM(md, false)
  900. tree, err = createDoc(box.ID, p, title, dom)
  901. if nil != err {
  902. return
  903. }
  904. ChangeFileTreeSort(box.ID, sorts)
  905. return
  906. }
  907. func CreateWithMarkdown(boxID, hPath, md, parentID string) (id string, err error) {
  908. box := Conf.Box(boxID)
  909. if nil == box {
  910. err = errors.New(Conf.Language(0))
  911. return
  912. }
  913. WaitForWritingFiles()
  914. luteEngine := util.NewLute()
  915. dom := luteEngine.Md2BlockDOM(md, false)
  916. id, _, err = createDocsByHPath(box.ID, hPath, dom, parentID)
  917. return
  918. }
  919. func GetHPathByPath(boxID, p string) (hPath string, err error) {
  920. if "/" == p {
  921. hPath = "/"
  922. return
  923. }
  924. luteEngine := util.NewLute()
  925. tree, err := filesys.LoadTree(boxID, p, luteEngine)
  926. if nil != err {
  927. return
  928. }
  929. hPath = tree.HPath
  930. return
  931. }
  932. func GetHPathsByPaths(paths []string) (hPaths []string, err error) {
  933. pathsBoxes := getBoxesByPaths(paths)
  934. for p, box := range pathsBoxes {
  935. if nil == box {
  936. logging.LogWarnf("box not found by path [%s]", p)
  937. continue
  938. }
  939. bt := treenode.GetBlockTreeByPath(p)
  940. if nil == bt {
  941. logging.LogWarnf("block tree not found by path [%s]", p)
  942. continue
  943. }
  944. hPaths = append(hPaths, box.Name+bt.HPath)
  945. }
  946. return
  947. }
  948. func GetHPathByID(id string) (hPath string, err error) {
  949. tree, err := loadTreeByBlockID(id)
  950. if nil != err {
  951. return
  952. }
  953. hPath = tree.HPath
  954. return
  955. }
  956. func GetFullHPathByID(id string) (hPath string, err error) {
  957. tree, err := loadTreeByBlockID(id)
  958. if nil != err {
  959. return
  960. }
  961. box := Conf.Box(tree.Box)
  962. var boxName string
  963. if nil != box {
  964. boxName = box.Name
  965. }
  966. hPath = boxName + tree.HPath
  967. return
  968. }
  969. func MoveDocs(fromPaths []string, toBoxID, toPath string, callback interface{}) (err error) {
  970. toBox := Conf.Box(toBoxID)
  971. if nil == toBox {
  972. err = errors.New(Conf.Language(0))
  973. return
  974. }
  975. fromPaths = util.FilterMoveDocFromPaths(fromPaths, toPath)
  976. if 1 > len(fromPaths) {
  977. return
  978. }
  979. pathsBoxes := getBoxesByPaths(fromPaths)
  980. // 检查路径深度是否超过限制
  981. for fromPath, fromBox := range pathsBoxes {
  982. childDepth := util.GetChildDocDepth(filepath.Join(util.DataDir, fromBox.ID, fromPath))
  983. if depth := strings.Count(toPath, "/") + childDepth; 6 < depth && !Conf.FileTree.AllowCreateDeeper {
  984. err = errors.New(Conf.Language(118))
  985. return
  986. }
  987. }
  988. needShowProgress := 16 < len(fromPaths)
  989. if needShowProgress {
  990. util.PushEndlessProgress(Conf.Language(116))
  991. }
  992. WaitForWritingFiles()
  993. luteEngine := util.NewLute()
  994. for fromPath, fromBox := range pathsBoxes {
  995. _, err = moveDoc(fromBox, fromPath, toBox, toPath, luteEngine, callback)
  996. if nil != err {
  997. return
  998. }
  999. }
  1000. cache.ClearDocsIAL()
  1001. IncSync()
  1002. if needShowProgress {
  1003. util.PushEndlessProgress(Conf.Language(113))
  1004. sql.WaitForWritingDatabase()
  1005. util.ReloadUI()
  1006. }
  1007. return
  1008. }
  1009. func moveDoc(fromBox *Box, fromPath string, toBox *Box, toPath string, luteEngine *lute.Lute, callback interface{}) (newPath string, err error) {
  1010. isSameBox := fromBox.ID == toBox.ID
  1011. if isSameBox {
  1012. if !fromBox.Exist(toPath) {
  1013. err = ErrBlockNotFound
  1014. return
  1015. }
  1016. } else {
  1017. if !toBox.Exist(toPath) {
  1018. err = ErrBlockNotFound
  1019. return
  1020. }
  1021. }
  1022. tree, err := filesys.LoadTree(fromBox.ID, fromPath, luteEngine)
  1023. if nil != err {
  1024. err = ErrBlockNotFound
  1025. return
  1026. }
  1027. moveToRoot := "/" == toPath
  1028. toBlockID := tree.ID
  1029. fromFolder := path.Join(path.Dir(fromPath), tree.ID)
  1030. toFolder := "/"
  1031. if !moveToRoot {
  1032. var toTree *parse.Tree
  1033. if isSameBox {
  1034. toTree, err = filesys.LoadTree(fromBox.ID, toPath, luteEngine)
  1035. } else {
  1036. toTree, err = filesys.LoadTree(toBox.ID, toPath, luteEngine)
  1037. }
  1038. if nil != err {
  1039. err = ErrBlockNotFound
  1040. return
  1041. }
  1042. toBlockID = toTree.ID
  1043. toFolder = path.Join(path.Dir(toPath), toBlockID)
  1044. }
  1045. if isSameBox {
  1046. if err = fromBox.MkdirAll(toFolder); nil != err {
  1047. return
  1048. }
  1049. } else {
  1050. if err = toBox.MkdirAll(toFolder); nil != err {
  1051. return
  1052. }
  1053. }
  1054. if fromBox.Exist(fromFolder) {
  1055. // 移动子文档文件夹
  1056. newFolder := path.Join(toFolder, tree.ID)
  1057. if isSameBox {
  1058. if err = fromBox.Move(fromFolder, newFolder); nil != err {
  1059. return
  1060. }
  1061. } else {
  1062. absFromPath := filepath.Join(util.DataDir, fromBox.ID, fromFolder)
  1063. absToPath := filepath.Join(util.DataDir, toBox.ID, newFolder)
  1064. if gulu.File.IsExist(absToPath) {
  1065. filelock.Remove(absToPath)
  1066. }
  1067. if err = filelock.Move(absFromPath, absToPath); nil != err {
  1068. msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
  1069. logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBox.ID, err)
  1070. err = errors.New(msg)
  1071. return
  1072. }
  1073. }
  1074. }
  1075. newPath = path.Join(toFolder, tree.ID+".sy")
  1076. if isSameBox {
  1077. if err = fromBox.Move(fromPath, newPath); nil != err {
  1078. return
  1079. }
  1080. tree, err = filesys.LoadTree(fromBox.ID, newPath, luteEngine)
  1081. if nil != err {
  1082. return
  1083. }
  1084. moveTree(tree)
  1085. } else {
  1086. absFromPath := filepath.Join(util.DataDir, fromBox.ID, fromPath)
  1087. absToPath := filepath.Join(util.DataDir, toBox.ID, newPath)
  1088. if err = filelock.Move(absFromPath, absToPath); nil != err {
  1089. msg := fmt.Sprintf(Conf.Language(5), fromBox.Name, fromPath, err)
  1090. logging.LogErrorf("move [path=%s] in box [%s] failed: %s", fromPath, fromBox.ID, err)
  1091. err = errors.New(msg)
  1092. return
  1093. }
  1094. tree, err = filesys.LoadTree(toBox.ID, newPath, luteEngine)
  1095. if nil != err {
  1096. return
  1097. }
  1098. moveTree(tree)
  1099. moveSorts(tree.ID, fromBox.ID, toBox.ID)
  1100. }
  1101. evt := util.NewCmdResult("moveDoc", 0, util.PushModeBroadcast)
  1102. evt.Data = map[string]interface{}{
  1103. "fromNotebook": fromBox.ID,
  1104. "fromPath": fromPath,
  1105. "toNotebook": toBox.ID,
  1106. "toPath": toPath,
  1107. "newPath": newPath,
  1108. }
  1109. evt.Callback = callback
  1110. util.PushEvent(evt)
  1111. return
  1112. }
  1113. func RemoveDoc(boxID, p string) {
  1114. box := Conf.Box(boxID)
  1115. if nil == box {
  1116. return
  1117. }
  1118. WaitForWritingFiles()
  1119. luteEngine := util.NewLute()
  1120. removeDoc(box, p, luteEngine)
  1121. IncSync()
  1122. return
  1123. }
  1124. func RemoveDocs(paths []string) {
  1125. util.PushEndlessProgress(Conf.Language(116))
  1126. defer util.PushClearProgress()
  1127. paths = util.FilterSelfChildDocs(paths)
  1128. pathsBoxes := getBoxesByPaths(paths)
  1129. WaitForWritingFiles()
  1130. luteEngine := util.NewLute()
  1131. for p, box := range pathsBoxes {
  1132. removeDoc(box, p, luteEngine)
  1133. }
  1134. return
  1135. }
  1136. func removeDoc(box *Box, p string, luteEngine *lute.Lute) {
  1137. tree, _ := filesys.LoadTree(box.ID, p, luteEngine)
  1138. if nil == tree {
  1139. return
  1140. }
  1141. historyDir, err := GetHistoryDir(HistoryOpDelete)
  1142. if nil != err {
  1143. logging.LogErrorf("get history dir failed: %s", err)
  1144. return
  1145. }
  1146. historyPath := filepath.Join(historyDir, box.ID, p)
  1147. absPath := filepath.Join(util.DataDir, box.ID, p)
  1148. if err = filelock.Copy(absPath, historyPath); nil != err {
  1149. logging.LogErrorf("backup [path=%s] to history [%s] failed: %s", absPath, historyPath, err)
  1150. return
  1151. }
  1152. copyDocAssetsToDataAssets(box.ID, p)
  1153. removeIDs := treenode.RootChildIDs(tree.ID)
  1154. dir := path.Dir(p)
  1155. childrenDir := path.Join(dir, tree.ID)
  1156. existChildren := box.Exist(childrenDir)
  1157. if existChildren {
  1158. absChildrenDir := filepath.Join(util.DataDir, tree.Box, childrenDir)
  1159. historyPath = filepath.Join(historyDir, tree.Box, childrenDir)
  1160. if err = filelock.Copy(absChildrenDir, historyPath); nil != err {
  1161. logging.LogErrorf("backup [path=%s] to history [%s] failed: %s", absChildrenDir, historyPath, err)
  1162. return
  1163. }
  1164. }
  1165. indexHistoryDir(filepath.Base(historyDir), util.NewLute())
  1166. if existChildren {
  1167. if err = box.Remove(childrenDir); nil != err {
  1168. return
  1169. }
  1170. }
  1171. if err = box.Remove(p); nil != err {
  1172. return
  1173. }
  1174. box.removeSort(removeIDs)
  1175. RemoveRecentDoc(removeIDs)
  1176. if "/" != dir {
  1177. others, err := os.ReadDir(filepath.Join(util.DataDir, box.ID, dir))
  1178. if nil == err && 1 > len(others) {
  1179. box.Remove(dir)
  1180. }
  1181. }
  1182. evt := util.NewCmdResult("removeDoc", 0, util.PushModeBroadcast)
  1183. evt.Data = map[string]interface{}{
  1184. "ids": removeIDs,
  1185. }
  1186. util.PushEvent(evt)
  1187. task.AppendTask(task.DatabaseIndex, removeDoc0, box, p, childrenDir)
  1188. }
  1189. func removeDoc0(box *Box, p, childrenDir string) {
  1190. treenode.RemoveBlockTreesByPathPrefix(childrenDir)
  1191. sql.RemoveTreePathQueue(box.ID, childrenDir)
  1192. cache.RemoveDocIAL(p)
  1193. return
  1194. }
  1195. func RenameDoc(boxID, p, title string) (err error) {
  1196. box := Conf.Box(boxID)
  1197. if nil == box {
  1198. err = errors.New(Conf.Language(0))
  1199. return
  1200. }
  1201. WaitForWritingFiles()
  1202. luteEngine := util.NewLute()
  1203. tree, err := filesys.LoadTree(box.ID, p, luteEngine)
  1204. if nil != err {
  1205. return
  1206. }
  1207. title = gulu.Str.RemoveInvisible(title)
  1208. if 512 < utf8.RuneCountInString(title) {
  1209. // 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299
  1210. return errors.New(Conf.Language(106))
  1211. }
  1212. oldTitle := tree.Root.IALAttr("title")
  1213. if oldTitle == title {
  1214. return
  1215. }
  1216. if "" == title {
  1217. title = "Untitled"
  1218. }
  1219. title = strings.ReplaceAll(title, "/", "")
  1220. tree.HPath = path.Join(path.Dir(tree.HPath), title)
  1221. tree.Root.SetIALAttr("title", title)
  1222. tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
  1223. if err = renameWriteJSONQueue(tree); nil != err {
  1224. return
  1225. }
  1226. refText := getNodeRefText(tree.Root)
  1227. evt := util.NewCmdResult("rename", 0, util.PushModeBroadcast)
  1228. evt.Data = map[string]interface{}{
  1229. "box": boxID,
  1230. "id": tree.Root.ID,
  1231. "path": p,
  1232. "title": title,
  1233. "refText": refText,
  1234. }
  1235. util.PushEvent(evt)
  1236. box.renameSubTrees(tree)
  1237. updateRefTextRenameDoc(tree)
  1238. IncSync()
  1239. return
  1240. }
  1241. func CreateDailyNote(boxID string) (p string, existed bool, err error) {
  1242. box := Conf.Box(boxID)
  1243. if nil == box {
  1244. err = ErrBoxNotFound
  1245. return
  1246. }
  1247. boxConf := box.GetConf()
  1248. if "" == boxConf.DailyNoteSavePath || "/" == boxConf.DailyNoteSavePath {
  1249. err = errors.New(Conf.Language(49))
  1250. return
  1251. }
  1252. hPath, err := RenderGoTemplate(boxConf.DailyNoteSavePath)
  1253. if nil != err {
  1254. return
  1255. }
  1256. WaitForWritingFiles()
  1257. existRoot := treenode.GetBlockTreeRootByHPath(box.ID, hPath)
  1258. if nil != existRoot {
  1259. existed = true
  1260. p = existRoot.Path
  1261. return
  1262. }
  1263. id, existed, err := createDocsByHPath(box.ID, hPath, "", "")
  1264. if nil != err {
  1265. return
  1266. }
  1267. var dom string
  1268. if "" != boxConf.DailyNoteTemplatePath {
  1269. tplPath := filepath.Join(util.DataDir, "templates", boxConf.DailyNoteTemplatePath)
  1270. if !gulu.File.IsExist(tplPath) {
  1271. logging.LogWarnf("not found daily note template [%s]", tplPath)
  1272. } else {
  1273. dom, err = renderTemplate(tplPath, id)
  1274. if nil != err {
  1275. logging.LogWarnf("render daily note template [%s] failed: %s", boxConf.DailyNoteTemplatePath, err)
  1276. }
  1277. }
  1278. }
  1279. if "" != dom {
  1280. var tree *parse.Tree
  1281. tree, err = loadTreeByBlockID(id)
  1282. if nil == err {
  1283. tree.Root.FirstChild.Unlink()
  1284. luteEngine := util.NewLute()
  1285. newTree := luteEngine.BlockDOM2Tree(dom)
  1286. var children []*ast.Node
  1287. for c := newTree.Root.FirstChild; nil != c; c = c.Next {
  1288. children = append(children, c)
  1289. }
  1290. for _, c := range children {
  1291. tree.Root.AppendChild(c)
  1292. }
  1293. tree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
  1294. if err = indexWriteJSONQueue(tree); nil != err {
  1295. return
  1296. }
  1297. }
  1298. }
  1299. IncSync()
  1300. b := treenode.GetBlockTree(id)
  1301. p = b.Path
  1302. return
  1303. }
  1304. func createDoc(boxID, p, title, dom string) (tree *parse.Tree, err error) {
  1305. title = gulu.Str.RemoveInvisible(title)
  1306. if 512 < utf8.RuneCountInString(title) {
  1307. // 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299
  1308. err = errors.New(Conf.Language(106))
  1309. return
  1310. }
  1311. title = strings.ReplaceAll(title, "/", "")
  1312. baseName := strings.TrimSpace(path.Base(p))
  1313. if "" == strings.TrimSuffix(baseName, ".sy") {
  1314. err = errors.New(Conf.Language(16))
  1315. return
  1316. }
  1317. if strings.HasPrefix(baseName, ".") {
  1318. err = errors.New(Conf.Language(13))
  1319. return
  1320. }
  1321. box := Conf.Box(boxID)
  1322. if nil == box {
  1323. err = errors.New(Conf.Language(0))
  1324. return
  1325. }
  1326. id := strings.TrimSuffix(path.Base(p), ".sy")
  1327. var hPath string
  1328. folder := path.Dir(p)
  1329. if "/" != folder {
  1330. parentID := path.Base(folder)
  1331. parentTree, loadErr := loadTreeByBlockID(parentID)
  1332. if nil != loadErr {
  1333. logging.LogErrorf("get parent tree [%s] failed", parentID)
  1334. err = ErrBlockNotFound
  1335. return
  1336. }
  1337. hPath = path.Join(parentTree.HPath, title)
  1338. } else {
  1339. hPath = "/" + title
  1340. }
  1341. if depth := strings.Count(p, "/"); 7 < depth && !Conf.FileTree.AllowCreateDeeper {
  1342. err = errors.New(Conf.Language(118))
  1343. return
  1344. }
  1345. if !box.Exist(folder) {
  1346. if err = box.MkdirAll(folder); nil != err {
  1347. return
  1348. }
  1349. }
  1350. if box.Exist(p) {
  1351. err = errors.New(Conf.Language(1))
  1352. return
  1353. }
  1354. luteEngine := util.NewLute()
  1355. tree = luteEngine.BlockDOM2Tree(dom)
  1356. tree.Box = boxID
  1357. tree.Path = p
  1358. tree.HPath = hPath
  1359. tree.ID = id
  1360. tree.Root.ID = id
  1361. tree.Root.Spec = "1"
  1362. updated := util.TimeFromID(id)
  1363. tree.Root.KramdownIAL = [][]string{{"id", id}, {"title", html.EscapeAttrVal(title)}, {"updated", updated}}
  1364. if nil == tree.Root.FirstChild {
  1365. tree.Root.AppendChild(treenode.NewParagraph())
  1366. }
  1367. transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
  1368. PerformTransactions(&[]*Transaction{transaction})
  1369. WaitForWritingFiles()
  1370. return
  1371. }
  1372. func moveSorts(rootID, fromBox, toBox string) {
  1373. root := treenode.GetBlockTree(rootID)
  1374. if nil == root {
  1375. return
  1376. }
  1377. fromRootSorts := map[string]int{}
  1378. ids := treenode.RootChildIDs(rootID)
  1379. fromConfPath := filepath.Join(util.DataDir, fromBox, ".siyuan", "sort.json")
  1380. fromFullSortIDs := map[string]int{}
  1381. if gulu.File.IsExist(fromConfPath) {
  1382. data, err := filelock.ReadFile(fromConfPath)
  1383. if nil != err {
  1384. logging.LogErrorf("read sort conf failed: %s", err)
  1385. return
  1386. }
  1387. if err = gulu.JSON.UnmarshalJSON(data, &fromFullSortIDs); nil != err {
  1388. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1389. }
  1390. }
  1391. for _, id := range ids {
  1392. fromRootSorts[id] = fromFullSortIDs[id]
  1393. }
  1394. toConfPath := filepath.Join(util.DataDir, toBox, ".siyuan", "sort.json")
  1395. toFullSortIDs := map[string]int{}
  1396. if gulu.File.IsExist(toConfPath) {
  1397. data, err := filelock.ReadFile(toConfPath)
  1398. if nil != err {
  1399. logging.LogErrorf("read sort conf failed: %s", err)
  1400. return
  1401. }
  1402. if err = gulu.JSON.UnmarshalJSON(data, &toFullSortIDs); nil != err {
  1403. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1404. return
  1405. }
  1406. }
  1407. for id, sortVal := range fromRootSorts {
  1408. toFullSortIDs[id] = sortVal
  1409. }
  1410. data, err := gulu.JSON.MarshalJSON(toFullSortIDs)
  1411. if nil != err {
  1412. logging.LogErrorf("marshal sort conf failed: %s", err)
  1413. return
  1414. }
  1415. if err = filelock.WriteFile(toConfPath, data); nil != err {
  1416. logging.LogErrorf("write sort conf failed: %s", err)
  1417. return
  1418. }
  1419. }
  1420. func ChangeFileTreeSort(boxID string, paths []string) {
  1421. if 1 > len(paths) {
  1422. return
  1423. }
  1424. WaitForWritingFiles()
  1425. box := Conf.Box(boxID)
  1426. sortIDs := map[string]int{}
  1427. max := 0
  1428. for i, p := range paths {
  1429. id := strings.TrimSuffix(path.Base(p), ".sy")
  1430. sortIDs[id] = i + 1
  1431. if i == len(paths)-1 {
  1432. max = i + 2
  1433. }
  1434. }
  1435. p := paths[0]
  1436. parentPath := path.Dir(p)
  1437. absParentPath := filepath.Join(util.DataDir, boxID, parentPath)
  1438. files, err := os.ReadDir(absParentPath)
  1439. if nil != err {
  1440. logging.LogErrorf("read dir [%s] failed: %s", absParentPath, err)
  1441. }
  1442. sortFolderIDs := map[string]int{}
  1443. for _, f := range files {
  1444. if !strings.HasSuffix(f.Name(), ".sy") {
  1445. continue
  1446. }
  1447. id := strings.TrimSuffix(f.Name(), ".sy")
  1448. val := sortIDs[id]
  1449. if 0 == val {
  1450. val = max
  1451. max++
  1452. }
  1453. sortFolderIDs[id] = val
  1454. }
  1455. confDir := filepath.Join(util.DataDir, box.ID, ".siyuan")
  1456. if err = os.MkdirAll(confDir, 0755); nil != err {
  1457. logging.LogErrorf("create conf dir failed: %s", err)
  1458. return
  1459. }
  1460. confPath := filepath.Join(confDir, "sort.json")
  1461. fullSortIDs := map[string]int{}
  1462. var data []byte
  1463. if gulu.File.IsExist(confPath) {
  1464. data, err = filelock.ReadFile(confPath)
  1465. if nil != err {
  1466. logging.LogErrorf("read sort conf failed: %s", err)
  1467. return
  1468. }
  1469. if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
  1470. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1471. }
  1472. }
  1473. for sortID, sortVal := range sortFolderIDs {
  1474. fullSortIDs[sortID] = sortVal
  1475. }
  1476. data, err = gulu.JSON.MarshalJSON(fullSortIDs)
  1477. if nil != err {
  1478. logging.LogErrorf("marshal sort conf failed: %s", err)
  1479. return
  1480. }
  1481. if err = filelock.WriteFile(confPath, data); nil != err {
  1482. logging.LogErrorf("write sort conf failed: %s", err)
  1483. return
  1484. }
  1485. IncSync()
  1486. }
  1487. func (box *Box) fillSort(files *[]*File) {
  1488. confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
  1489. if !gulu.File.IsExist(confPath) {
  1490. return
  1491. }
  1492. data, err := filelock.ReadFile(confPath)
  1493. if nil != err {
  1494. logging.LogErrorf("read sort conf failed: %s", err)
  1495. return
  1496. }
  1497. fullSortIDs := map[string]int{}
  1498. if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
  1499. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1500. return
  1501. }
  1502. for _, f := range *files {
  1503. id := strings.TrimSuffix(f.ID, ".sy")
  1504. f.Sort = fullSortIDs[id]
  1505. }
  1506. }
  1507. func (box *Box) removeSort(ids []string) {
  1508. confPath := filepath.Join(util.DataDir, box.ID, ".siyuan", "sort.json")
  1509. if !gulu.File.IsExist(confPath) {
  1510. return
  1511. }
  1512. data, err := filelock.ReadFile(confPath)
  1513. if nil != err {
  1514. logging.LogErrorf("read sort conf failed: %s", err)
  1515. return
  1516. }
  1517. fullSortIDs := map[string]int{}
  1518. if err = gulu.JSON.UnmarshalJSON(data, &fullSortIDs); nil != err {
  1519. logging.LogErrorf("unmarshal sort conf failed: %s", err)
  1520. return
  1521. }
  1522. for _, toRemove := range ids {
  1523. delete(fullSortIDs, toRemove)
  1524. }
  1525. data, err = gulu.JSON.MarshalJSON(fullSortIDs)
  1526. if nil != err {
  1527. logging.LogErrorf("marshal sort conf failed: %s", err)
  1528. return
  1529. }
  1530. if err = filelock.WriteFile(confPath, data); nil != err {
  1531. logging.LogErrorf("write sort conf failed: %s", err)
  1532. return
  1533. }
  1534. }