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
+ }
+ }
+ }
+}