A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 272 lines 6.4 kB view raw
1// SiYuan - Refactor your thinking 2// Copyright (c) 2020-present, b3log.org 3// 4// This program is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Affero General Public License as published by 6// the Free Software Foundation, either version 3 of the License, or 7// (at your option) any later version. 8// 9// This program is distributed in the hope that it will be useful, 10// but WITHOUT ANY WARRANTY; without even the implied warranty of 11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12// GNU Affero General Public License for more details. 13// 14// You should have received a copy of the GNU Affero General Public License 15// along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17package model 18 19import ( 20 "errors" 21 "fmt" 22 "os" 23 "path/filepath" 24 "strings" 25 "sync" 26 "time" 27 "unicode/utf8" 28 29 "github.com/88250/gulu" 30 "github.com/88250/lute/ast" 31 "github.com/siyuan-note/filelock" 32 "github.com/siyuan-note/logging" 33 "github.com/siyuan-note/siyuan/kernel/task" 34 "github.com/siyuan-note/siyuan/kernel/util" 35) 36 37func CreateBox(name string) (id string, err error) { 38 name = util.RemoveInvalid(name) 39 if 512 < utf8.RuneCountInString(name) { 40 // 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299 41 err = errors.New(Conf.Language(106)) 42 return 43 } 44 if "" == name { 45 name = Conf.language(105) 46 } 47 48 FlushTxQueue() 49 50 createDocLock.Lock() 51 defer createDocLock.Unlock() 52 53 boxes, _ := ListNotebooks() 54 for i, b := range boxes { 55 c := b.GetConf() 56 c.Sort = i + 1 57 b.SaveConf(c) 58 } 59 60 id = ast.NewNodeID() 61 boxLocalPath := filepath.Join(util.DataDir, id) 62 err = os.MkdirAll(boxLocalPath, 0755) 63 if err != nil { 64 return 65 } 66 67 box := &Box{ID: id, Name: name} 68 boxConf := box.GetConf() 69 boxConf.Name = name 70 box.SaveConf(boxConf) 71 IncSync() 72 logging.LogInfof("created box [%s]", id) 73 return 74} 75 76func RenameBox(boxID, name string) (err error) { 77 box := Conf.Box(boxID) 78 if nil == box { 79 return errors.New(Conf.Language(0)) 80 } 81 82 if 512 < utf8.RuneCountInString(name) { 83 // 限制笔记本名和文档名最大长度为 `512` https://github.com/siyuan-note/siyuan/issues/6299 84 err = errors.New(Conf.Language(106)) 85 return 86 } 87 88 if "" == name { 89 name = Conf.language(105) 90 } 91 92 boxConf := box.GetConf() 93 boxConf.Name = name 94 box.Name = name 95 box.SaveConf(boxConf) 96 IncSync() 97 logging.LogInfof("renamed box [%s] to [%s]", boxID, name) 98 return 99} 100 101var boxLock = sync.Map{} 102 103func RemoveBox(boxID string) (err error) { 104 if _, ok := boxLock.Load(boxID); ok { 105 err = fmt.Errorf(Conf.language(239)) 106 return 107 } 108 109 boxLock.Store(boxID, true) 110 defer boxLock.Delete(boxID) 111 112 if util.IsReservedFilename(boxID) { 113 return errors.New(fmt.Sprintf("can not remove [%s] caused by it is a reserved file", boxID)) 114 } 115 116 FlushTxQueue() 117 isUserGuide := IsUserGuide(boxID) 118 createDocLock.Lock() 119 defer createDocLock.Unlock() 120 121 localPath := filepath.Join(util.DataDir, boxID) 122 if !filelock.IsExist(localPath) { 123 return 124 } 125 if !gulu.File.IsDir(localPath) { 126 return errors.New(fmt.Sprintf("can not remove [%s] caused by it is not a dir", boxID)) 127 } 128 129 if !isUserGuide { 130 var historyDir string 131 historyDir, err = GetHistoryDir(HistoryOpDelete) 132 if err != nil { 133 logging.LogErrorf("get history dir failed: %s", err) 134 return 135 } 136 p := strings.TrimPrefix(localPath, util.DataDir) 137 historyPath := filepath.Join(historyDir, p) 138 if err = filelock.Copy(localPath, historyPath); err != nil { 139 logging.LogErrorf("gen sync history failed: %s", err) 140 return 141 } 142 143 copyBoxAssetsToDataAssets(boxID) 144 } 145 146 unmount0(boxID) 147 if err = filelock.Remove(localPath); err != nil { 148 return 149 } 150 IncSync() 151 152 logging.LogInfof("removed box [%s]", boxID) 153 return 154} 155 156func Unmount(boxID string) { 157 FlushTxQueue() 158 159 unmount0(boxID) 160 evt := util.NewCmdResult("unmount", 0, util.PushModeBroadcast) 161 evt.Data = map[string]interface{}{ 162 "box": boxID, 163 } 164 util.PushEvent(evt) 165} 166 167func unmount0(boxID string) { 168 box := Conf.Box(boxID) 169 if nil == box { 170 return 171 } 172 173 boxConf := box.GetConf() 174 boxConf.Closed = true 175 box.SaveConf(boxConf) 176 box.Unindex() 177} 178 179func Mount(boxID string) (alreadyMount bool, err error) { 180 if _, ok := boxLock.Load(boxID); ok { 181 err = fmt.Errorf(Conf.language(239)) 182 return 183 } 184 185 boxLock.Store(boxID, true) 186 defer boxLock.Delete(boxID) 187 188 FlushTxQueue() 189 isUserGuide := IsUserGuide(boxID) 190 191 localPath := filepath.Join(util.DataDir, boxID) 192 var reMountGuide bool 193 if isUserGuide { 194 // 重新挂载帮助文档 195 196 guideBox := Conf.Box(boxID) 197 if nil != guideBox { 198 unmount0(guideBox.ID) 199 reMountGuide = true 200 } 201 202 if err = filelock.Remove(localPath); err != nil { 203 return 204 } 205 206 boxes, _ := ListNotebooks() 207 var sort int 208 if len(boxes) > 0 { 209 sort = boxes[0].Sort - 1 210 } 211 212 p := filepath.Join(util.WorkingDir, "guide", boxID) 213 if err = filelock.Copy(p, localPath); err != nil { 214 return 215 } 216 217 avDirPath := filepath.Join(util.WorkingDir, "guide", boxID, "storage", "av") 218 if filelock.IsExist(avDirPath) { 219 if err = filelock.Copy(avDirPath, filepath.Join(util.DataDir, "storage", "av")); err != nil { 220 return 221 } 222 } 223 224 if box := Conf.Box(boxID); nil != box { 225 boxConf := box.GetConf() 226 boxConf.Closed = true 227 boxConf.Sort = sort 228 box.SaveConf(boxConf) 229 } 230 231 if Conf.OpenHelp { 232 Conf.OpenHelp = false 233 Conf.Save() 234 } 235 236 task.AppendAsyncTaskWithDelay(task.PushMsg, 3*time.Second, util.PushErrMsg, Conf.Language(52), 7000) 237 go func() { 238 // 每次打开帮助文档时自动检查版本更新并提醒 https://github.com/siyuan-note/siyuan/issues/5057 239 time.Sleep(time.Second * 10) 240 CheckUpdate(true) 241 }() 242 } 243 244 if !gulu.File.IsDir(localPath) { 245 return false, errors.New("can not open file, just support open folder only") 246 } 247 248 for _, box := range Conf.GetOpenedBoxes() { 249 if box.ID == boxID { 250 return true, nil 251 } 252 } 253 254 box := &Box{ID: boxID} 255 boxConf := box.GetConf() 256 boxConf.Closed = false 257 box.SaveConf(boxConf) 258 259 box.Index() 260 // 缓存根一级的文档树展开 261 ListDocTree(box.ID, "/", util.SortModeUnassigned, false, false, Conf.FileTree.MaxListCount) 262 util.ClearPushProgress(100) 263 264 if reMountGuide { 265 return true, nil 266 } 267 return false, nil 268} 269 270func IsUserGuide(boxID string) bool { 271 return "20210808180117-czj9bvb" == boxID || "20210808180117-6v0mkxr" == boxID || "20211226090932-5lcq56f" == boxID || "20240530133126-axarxgx" == boxID 272}