Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358// SiYuan - Refactor your thinking// Copyright (c) 2020-present, b3log.org//// This program is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// This program is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with this program. If not, see <https://www.gnu.org/licenses/>.
package api
import ( "fmt" "net/http" "os" "path/filepath" "sort" "strings" "time" "unicode/utf8"
"github.com/88250/gulu" "github.com/gin-gonic/gin" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/model" "github.com/siyuan-note/siyuan/kernel/util")
func checkWorkspaceDir(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
path := arg["path"].(string) // 检查路径是否是分区根路径 if util.IsPartitionRootPath(path) { ret.Code = -1 ret.Msg = model.Conf.Language(273) ret.Data = map[string]interface{}{"closeTimeout": 7000} return }
// 检查路径是否包含其他文件 if !util.IsWorkspaceDir(path) { entries, err := os.ReadDir(path) if err != nil { ret.Code = -1 ret.Msg = fmt.Sprintf("read dir [%s] failed: %s", path, err) return } if 0 < len(entries) { ret.Code = -1 ret.Msg = model.Conf.Language(274) ret.Data = map[string]interface{}{"closeTimeout": 7000} return } }
if isInvalidWorkspacePath(path) { ret.Code = -1 ret.Msg = "This workspace name is not allowed, please use another name" return }
if !gulu.File.IsExist(path) { ret.Code = -1 ret.Msg = "This workspace does not exist" return }
ret.Data = map[string]interface{}{ "isWorkspace": util.IsWorkspaceDir(path), }}
func createWorkspaceDir(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
absPath := arg["path"].(string) absPath = util.RemoveInvalid(absPath) absPath = strings.TrimSpace(absPath) if isInvalidWorkspacePath(absPath) { ret.Code = -1 ret.Msg = "This workspace name is not allowed, please use another name" return }
if !gulu.File.IsExist(absPath) { if err := os.MkdirAll(absPath, 0755); err != nil { ret.Code = -1 ret.Msg = fmt.Sprintf("create workspace dir [%s] failed: %s", absPath, err) return } }
workspacePaths, err := util.ReadWorkspacePaths() if err != nil { ret.Code = -1 ret.Msg = err.Error() return }
workspacePaths = append(workspacePaths, absPath)
if err = util.WriteWorkspacePaths(workspacePaths); err != nil { ret.Code = -1 ret.Msg = err.Error() return }}
func removeWorkspaceDir(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
path := arg["path"].(string)
if util.IsWorkspaceLocked(path) || util.WorkspaceDir == path { msg := "Cannot remove current workspace" ret.Code = -1 ret.Msg = msg ret.Data = map[string]interface{}{"closeTimeout": 3000} return }
workspacePaths, err := util.ReadWorkspacePaths() if err != nil { ret.Code = -1 ret.Msg = err.Error() return }
workspacePaths = gulu.Str.RemoveElem(workspacePaths, path)
if err = util.WriteWorkspacePaths(workspacePaths); err != nil { ret.Code = -1 ret.Msg = err.Error() return }}
func removeWorkspaceDirPhysically(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
path := arg["path"].(string) if gulu.File.IsDir(path) { err := os.RemoveAll(path) if err != nil { ret.Code = -1 ret.Msg = err.Error() return } }
logging.LogInfof("removed workspace [%s] physically", path) if util.WorkspaceDir == path { os.Exit(logging.ExitCodeOk) }}
type Workspace struct { Path string `json:"path"` Closed bool `json:"closed"`}
func getMobileWorkspaces(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
if util.ContainerIOS != util.Container && util.ContainerAndroid != util.Container && util.ContainerHarmony != util.Container { return }
root := filepath.Dir(util.WorkspaceDir) dirs, err := os.ReadDir(root) if err != nil { logging.LogErrorf("read dir [%s] failed: %s", root, err) ret.Code = -1 ret.Msg = err.Error() return }
ret.Data = []string{} var paths []string for _, dir := range dirs { if dir.IsDir() { absPath := filepath.Join(root, dir.Name()) if isInvalidWorkspacePath(absPath) { continue }
paths = append(paths, absPath) } } ret.Data = paths}
func getWorkspaces(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
workspacePaths, err := util.ReadWorkspacePaths() if err != nil { ret.Code = -1 ret.Msg = err.Error() return }
if role := model.GetGinContextRole(c); !model.IsValidRole(role, []model.Role{ model.RoleAdministrator, }) { ret.Data = []*Workspace{} return }
var workspaces, openedWorkspaces, closedWorkspaces []*Workspace for _, p := range workspacePaths { closed := !util.IsWorkspaceLocked(p) if closed { closedWorkspaces = append(closedWorkspaces, &Workspace{Path: p, Closed: closed}) } else { openedWorkspaces = append(openedWorkspaces, &Workspace{Path: p, Closed: closed}) } } sort.Slice(openedWorkspaces, func(i, j int) bool { return util.NaturalCompare(filepath.Base(openedWorkspaces[i].Path), filepath.Base(openedWorkspaces[j].Path)) }) sort.Slice(closedWorkspaces, func(i, j int) bool { return util.NaturalCompare(filepath.Base(closedWorkspaces[i].Path), filepath.Base(closedWorkspaces[j].Path)) }) workspaces = append(workspaces, openedWorkspaces...) workspaces = append(workspaces, closedWorkspaces...) ret.Data = workspaces}
func setWorkspaceDir(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
path := arg["path"].(string) if util.WorkspaceDir == path { ret.Code = -1 ret.Msg = model.Conf.Language(78) ret.Data = map[string]interface{}{"closeTimeout": 3000} return }
if util.IsCloudDrivePath(path) { ret.Code = -1 ret.Msg = model.Conf.Language(196) ret.Data = map[string]interface{}{"closeTimeout": 7000} return }
if gulu.OS.IsWindows() { // 改进判断工作空间路径实现 https://github.com/siyuan-note/siyuan/issues/7569 installDirLower := strings.ToLower(filepath.Dir(util.WorkingDir)) pathLower := strings.ToLower(path) if strings.HasPrefix(pathLower, installDirLower) && (util.IsSubPath(installDirLower, pathLower) || filepath.Clean(installDirLower) == filepath.Clean(pathLower)) { ret.Code = -1 ret.Msg = model.Conf.Language(98) ret.Data = map[string]interface{}{"closeTimeout": 5000} return } }
// 检查路径是否在已有的工作空间路径中 pathIsWorkspace := util.IsWorkspaceDir(path) if !pathIsWorkspace { for p := filepath.Dir(path); !util.IsPartitionRootPath(p); p = filepath.Dir(p) { if util.IsWorkspaceDir(p) { ret.Code = -1 ret.Msg = fmt.Sprintf(model.Conf.Language(256), path, p) ret.Data = map[string]interface{}{"closeTimeout": 7000} return } } }
workspacePaths, err := util.ReadWorkspacePaths() if err != nil { ret.Code = -1 ret.Msg = err.Error() return }
workspacePaths = append(workspacePaths, path) workspacePaths = gulu.Str.RemoveDuplicatedElem(workspacePaths) workspacePaths = gulu.Str.RemoveElem(workspacePaths, path) workspacePaths = append(workspacePaths, path) // 切换的工作空间固定放在最后一个
if err = util.WriteWorkspacePaths(workspacePaths); err != nil { ret.Code = -1 ret.Msg = err.Error() return }
if util.ContainerAndroid == util.Container || util.ContainerIOS == util.Container || util.ContainerHarmony == util.Container { util.PushMsg(model.Conf.Language(42), 1000*15) time.Sleep(2 * time.Second) }}
func isInvalidWorkspacePath(absPath string) bool { if "" == absPath { return true } name := filepath.Base(absPath) if "" == name { return true } if strings.HasPrefix(name, ".") { return true } if !gulu.File.IsValidFilename(name) { return true } if 32 < utf8.RuneCountInString(name) { // Adjust workspace name length limit to 32 runes https://github.com/siyuan-note/siyuan/issues/9440 return true } toLower := strings.ToLower(name) return gulu.Str.Contains(toLower, []string{"conf", "home", "data", "temp"})}