// SiYuan - Build Your Eternal Digital Garden // 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 . package util import ( "fmt" "math/rand" "os" "path/filepath" "reflect" "runtime" "runtime/debug" "strings" "sync" "time" "github.com/88250/gulu" "github.com/denisbrodbeck/machineid" "github.com/siyuan-note/httpclient" "github.com/siyuan-note/logging" ) const DatabaseVer = "20220501" // 修改表结构的话需要修改这里 const ( ExitCodeReadOnlyDatabase = 20 // 数据库文件被锁 ExitCodeUnavailablePort = 21 // 端口不可用 ExitCodeCreateConfDirErr = 22 // 创建配置目录失败 ExitCodeBlockTreeErr = 23 // 无法读写 blocktree.msgpack 文件 ExitCodeWorkspaceLocked = 24 // 工作空间已被锁定 ExitCodeCreateWorkspaceDirErr = 25 // 创建工作空间失败 ExitCodeFileSysInconsistent = 26 // 文件系统不一致 ExitCodeOk = 0 // 正常退出 ExitCodeFatal = 1 // 致命错误 ) // IsExiting 是否正在退出程序。 var IsExiting = false // MobileOSVer 移动端操作系统版本。 var MobileOSVer string func logBootInfo() { plat, platVer := GetOSPlatform() osInfo := plat if "" != platVer { osInfo += " " + platVer } logging.LogInfof("kernel is booting:\n"+ " * ver [%s]\n"+ " * arch [%s]\n"+ " * os [%s]\n"+ " * pid [%d]\n"+ " * runtime mode [%s]\n"+ " * working directory [%s]\n"+ " * read only [%v]\n"+ " * container [%s]\n"+ " * database [ver=%s]\n"+ " * workspace directory [%s]", Ver, runtime.GOARCH, osInfo, os.Getpid(), Mode, WorkingDir, ReadOnly, Container, DatabaseVer, WorkspaceDir) } func IsMutexLocked(m *sync.Mutex) bool { state := reflect.ValueOf(m).Elem().FieldByName("state") return state.Int()&1 == 1 } func RandomSleep(minMills, maxMills int) { r := gulu.Rand.Int(minMills, maxMills) time.Sleep(time.Duration(r) * time.Millisecond) } func GetDeviceID() string { if ContainerStd == Container { machineID, err := machineid.ID() if nil != err { return gulu.Rand.String(12) } return machineID } return gulu.Rand.String(12) } func SetNetworkProxy(proxyURL string) { if err := os.Setenv("HTTPS_PROXY", proxyURL); nil != err { logging.LogErrorf("set env [HTTPS_PROXY] failed: %s", err) } if err := os.Setenv("HTTP_PROXY", proxyURL); nil != err { logging.LogErrorf("set env [HTTP_PROXY] failed: %s", err) } if "" != proxyURL { logging.LogInfof("use network proxy [%s]", proxyURL) } else { logging.LogInfof("use network proxy [system]") } httpclient.CloseIdleConnections() } const ( // FrontendQueueInterval 为前端请求队列轮询间隔。 FrontendQueueInterval = 512 * time.Millisecond // SQLFlushInterval 为数据库事务队列写入间隔。 SQLFlushInterval = 3000 * time.Millisecond ) var ( Langs = map[string]map[int]string{} TimeLangs = map[string]map[string]interface{}{} TaskActionLangs = map[string]map[string]interface{}{} ) var ( thirdPartySyncCheckTicker = time.NewTicker(time.Minute * 30) firstThirdPartySyncCheck = true ) func CheckFileSysStatus() { if ContainerStd != Container { return } if firstThirdPartySyncCheck { firstThirdPartySyncCheck = false time.Sleep(time.Second * 10) } reportFileSysFatalError := func(err error) { stack := debug.Stack() logging.LogErrorf("check file system status failed: %s, %s", err, stack) os.Exit(ExitCodeFileSysInconsistent) } const fileSysStatusCheckFile = ".siyuan/filesys_status_check" for { 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(200 * time.Millisecond) if err = os.Rename(tmp, tmp+"_1"); nil != err { reportFileSysFatalError(err) break } time.Sleep(200 * 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 } <-thirdPartySyncCheckTicker.C } } }