diff --git a/app/electron/main.js b/app/electron/main.js index 19a62513b..84cb32fc5 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -482,6 +482,11 @@ const initKernel = (workspace, port, lang) => { "⚠️ 创建工作空间目录失败 Failed to create workspace directory", "
创建工作空间目录失败。
Failed to create workspace directory.
"); break; + case 26: + showErrorWindow( + "⚠️ 文件系统不一致 File system inconsistent", + "
请勿使用第三方同步盘进行数据同步,否则数据会被损坏(OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等)
Do not use a third-party sync disk for data sync, otherwise the data will be damaged (OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.)
"); + break; case 0: case 1: // Fatal error break; diff --git a/kernel/main.go b/kernel/main.go index 7892b4a71..1cfcfd468 100644 --- a/kernel/main.go +++ b/kernel/main.go @@ -48,6 +48,7 @@ func main() { job.StartCron() go model.AutoGenerateDocHistory() go cache.LoadAssets() + go util.CheckFileSysStatus() model.WatchAssets() model.HandleSignal() diff --git a/kernel/model/conf.go b/kernel/model/conf.go index cf31474d1..06da62cb8 100644 --- a/kernel/model/conf.go +++ b/kernel/model/conf.go @@ -32,7 +32,7 @@ import ( "github.com/88250/lute" "github.com/88250/lute/ast" "github.com/Xuanwo/go-locale" - humanize "github.com/dustin/go-humanize" + "github.com/dustin/go-humanize" "github.com/getsentry/sentry-go" "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" @@ -58,7 +58,8 @@ type AppConf struct { Editor *conf.Editor `json:"editor"` // 编辑器配置 Export *conf.Export `json:"export"` // 导出配置 Graph *conf.Graph `json:"graph"` // 关系图配置 - UILayout *conf.UILayout `json:"uiLayout"` // 界面布局 + UILayout *conf.UILayout `json:"uiLayout"` // 界面布局,v2.8.0 后这个字段不再使用 + UILayouts []*conf.UILayout `json:"uiLayouts"` // 界面布局列表 UserData string `json:"userData"` // 社区用户信息,对 User 加密存储 User *conf.User `json:"-"` // 社区用户内存结构,不持久化 Account *conf.Account `json:"account"` // 帐号配置 @@ -149,6 +150,9 @@ func InitConf() { if nil == Conf.UILayout { Conf.UILayout = &conf.UILayout{} } + if 1 > len(Conf.UILayouts) { + Conf.UILayouts = []*conf.UILayout{} + } if nil == Conf.Keymap { Conf.Keymap = &conf.Keymap{} } diff --git a/kernel/util/runtime.go b/kernel/util/runtime.go index 336b7e310..63155286f 100644 --- a/kernel/util/runtime.go +++ b/kernel/util/runtime.go @@ -17,9 +17,14 @@ package util import ( + "fmt" + "math/rand" "os" + "path/filepath" "reflect" "runtime" + "runtime/debug" + "strings" "sync" "time" @@ -38,6 +43,7 @@ const ( ExitCodeBlockTreeErr = 23 // 无法读写 blocktree.msgpack 文件 ExitCodeWorkspaceLocked = 24 // 工作空间已被锁定 ExitCodeCreateWorkspaceDirErr = 25 // 创建工作空间失败 + ExitCodeFileSysInconsistent = 26 // 文件系统不一致 ExitCodeOk = 0 // 正常退出 ExitCodeFatal = 1 // 致命错误 ) @@ -119,3 +125,106 @@ var ( TimeLangs = map[string]map[string]interface{}{} TaskActionLangs = map[string]map[string]interface{}{} ) + +var thirdPartySyncCheckTicker = time.NewTicker(time.Second * 10) + +func CheckFileSysStatus() { + if ContainerStd != Container { + return + } + + reportFileSysFatalError := func(err error) { + stack := debug.Stack() + logging.LogErrorf("check file system status failed: %s, %s", err, stack) + os.Exit(ExitCodeFileSysInconsistent) + } + + const fileSysStatusCheckFile = "filesys_status_check" + + for { + <-thirdPartySyncCheckTicker.C + + workspaceDirLower := strings.ToLower(WorkspaceDir) + if strings.Contains(workspaceDirLower, "onedrive") || strings.Contains(workspaceDirLower, "dropbox") || + strings.Contains(workspaceDirLower, "google drive") || strings.Contains(workspaceDirLower, "pcloud") { + reportFileSysFatalError(fmt.Errorf("workspace dir [%s] is in third party sync dir", WorkspaceDir)) + continue + } + + dir := filepath.Join(DataDir, fileSysStatusCheckFile) + if err := os.RemoveAll(dir); nil != err { + reportFileSysFatalError(err) + continue + } + + if err := os.MkdirAll(dir, 0755); nil != err { + reportFileSysFatalError(err) + continue + } + + for i := 0; i < 32; i++ { + tmp := filepath.Join(dir, "check_"+gulu.Rand.String(7)) + data := make([]byte, 1024*4) + _, err := rand.Read(data) + if nil != err { + reportFileSysFatalError(err) + break + } + + if err = os.WriteFile(tmp, data, 0644); nil != err { + reportFileSysFatalError(err) + break + } + + time.Sleep(time.Second) + + for j := 0; j < 32; j++ { + f, err := os.Open(tmp) + if nil != err { + reportFileSysFatalError(err) + break + } + + if err = f.Close(); nil != err { + reportFileSysFatalError(err) + break + } + + time.Sleep(100 * time.Millisecond) + + if err = os.Rename(tmp, tmp+"_1"); nil != err { + reportFileSysFatalError(err) + break + } + + time.Sleep(100 * time.Millisecond) + if err = os.Rename(tmp+"_1", tmp); nil != err { + reportFileSysFatalError(err) + break + } + } + + entries, err := os.ReadDir(dir) + if nil != err { + reportFileSysFatalError(err) + break + } + + count := 0 + for _, entry := range entries { + if !entry.IsDir() && strings.Contains(entry.Name(), "check_") { + count++ + } + } + if 1 < count { + reportFileSysFatalError(fmt.Errorf("dir [%s] has more than 1 file", dir)) + break + } + + if err = os.RemoveAll(tmp); nil != err { + reportFileSysFatalError(err) + break + } + } + } +}