file.go 53 KB

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