siyuan/kernel/model/conf.go

871 lines
24 KiB
Go
Raw Normal View History

// 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 <https://www.gnu.org/licenses/>.
package model
import (
"bytes"
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/88250/gulu"
"github.com/88250/lute"
2023-03-07 09:49:36 +08:00
"github.com/88250/lute/ast"
"github.com/Xuanwo/go-locale"
"github.com/dustin/go-humanize"
"github.com/getsentry/sentry-go"
"github.com/siyuan-note/filelock"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/sql"
2023-03-07 14:51:22 +08:00
"github.com/siyuan-note/siyuan/kernel/task"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
2023-03-07 09:49:36 +08:00
"golang.org/x/mod/semver"
"golang.org/x/text/language"
)
var Conf *AppConf
// AppConf 维护应用元数据,保存在 ~/.siyuan/conf.json。
type AppConf struct {
LogLevel string `json:"logLevel"` // 日志级别Off, Trace, Debug, Info, Warn, Error, Fatal
Appearance *conf.Appearance `json:"appearance"` // 外观
Langs []*conf.Lang `json:"langs"` // 界面语言列表
Lang string `json:"lang"` // 选择的界面语言,同 Appearance.Lang
FileTree *conf.FileTree `json:"fileTree"` // 文档面板
Tag *conf.Tag `json:"tag"` // 标签面板
Editor *conf.Editor `json:"editor"` // 编辑器配置
Export *conf.Export `json:"export"` // 导出配置
Graph *conf.Graph `json:"graph"` // 关系图配置
UILayout *conf.UILayout `json:"uiLayout"` // 界面布局v2.8.0 后这个字段不再使用
UserData string `json:"userData"` // 社区用户信息,对 User 加密存储
User *conf.User `json:"-"` // 社区用户内存结构,不持久化
Account *conf.Account `json:"account"` // 帐号配置
ReadOnly bool `json:"readonly"` // 是否是以只读模式运行
LocalIPs []string `json:"localIPs"` // 本地 IP 列表
AccessAuthCode string `json:"accessAuthCode"` // 访问授权码
System *conf.System `json:"system"` // 系统配置
Keymap *conf.Keymap `json:"keymap"` // 快捷键配置
Sync *conf.Sync `json:"sync"` // 同步配置
Search *conf.Search `json:"search"` // 搜索配置
Flashcard *conf.Flashcard `json:"flashcard"` // 闪卡配置
AI *conf.AI `json:"ai"` // 人工智能配置
Stat *conf.Stat `json:"stat"` // 统计
Api *conf.API `json:"api"` // API
Repo *conf.Repo `json:"repo"` // 数据仓库
OpenHelp bool `json:"openHelp"` // 启动后是否需要打开用户指南
}
func InitConf() {
initLang()
windowStateConf := filepath.Join(util.ConfDir, "windowState.json")
if !gulu.File.IsExist(windowStateConf) {
if err := gulu.File.WriteFileSafer(windowStateConf, []byte("{}"), 0644); nil != err {
logging.LogErrorf("create [windowState.json] failed: %s", err)
}
}
Conf = &AppConf{LogLevel: "debug"}
confPath := filepath.Join(util.ConfDir, "conf.json")
if gulu.File.IsExist(confPath) {
data, err := os.ReadFile(confPath)
if nil != err {
logging.LogErrorf("load conf [%s] failed: %s", confPath, err)
}
err = gulu.JSON.UnmarshalJSON(data, Conf)
if err != nil {
logging.LogErrorf("parse conf [%s] failed: %s", confPath, err)
}
}
if "" != util.Lang {
Conf.Lang = util.Lang
logging.LogInfof("initialized the specified language [%s]", util.Lang)
} else {
if "" == Conf.Lang {
// 未指定外观语言时使用系统语言
if userLang, err := locale.Detect(); nil == err {
var supportLangs []language.Tag
for lang := range util.Langs {
if tag, err := language.Parse(lang); nil == err {
supportLangs = append(supportLangs, tag)
} else {
logging.LogErrorf("load language [%s] failed: %s", lang, err)
}
}
matcher := language.NewMatcher(supportLangs)
lang, _, _ := matcher.Match(userLang)
base, _ := lang.Base()
region, _ := lang.Region()
util.Lang = base.String() + "_" + region.String()
Conf.Lang = util.Lang
logging.LogInfof("initialized language [%s] based on device locale", Conf.Lang)
} else {
logging.LogDebugf("check device locale failed [%s], using default language [en_US]", err)
util.Lang = "en_US"
Conf.Lang = util.Lang
}
}
util.Lang = Conf.Lang
}
Conf.Langs = loadLangs()
if nil == Conf.Appearance {
Conf.Appearance = conf.NewAppearance()
}
var langOK bool
for _, l := range Conf.Langs {
if Conf.Lang == l.Name {
langOK = true
break
}
}
if !langOK {
Conf.Lang = "en_US"
util.Lang = Conf.Lang
}
Conf.Appearance.Lang = Conf.Lang
if nil == Conf.UILayout {
Conf.UILayout = &conf.UILayout{}
}
if nil == Conf.Keymap {
Conf.Keymap = &conf.Keymap{}
}
if "" == Conf.Appearance.CodeBlockThemeDark {
Conf.Appearance.CodeBlockThemeDark = "dracula"
}
if "" == Conf.Appearance.CodeBlockThemeLight {
Conf.Appearance.CodeBlockThemeLight = "github"
}
if nil == Conf.FileTree {
Conf.FileTree = conf.NewFileTree()
}
if 1 > Conf.FileTree.MaxListCount {
Conf.FileTree.MaxListCount = 512
}
if 1 > Conf.FileTree.MaxOpenTabCount {
Conf.FileTree.MaxOpenTabCount = 8
}
if 32 < Conf.FileTree.MaxOpenTabCount {
Conf.FileTree.MaxOpenTabCount = 32
}
Conf.FileTree.DocCreateSavePath = strings.TrimSpace(Conf.FileTree.DocCreateSavePath)
for strings.HasSuffix(Conf.FileTree.DocCreateSavePath, "/") {
Conf.FileTree.DocCreateSavePath = strings.TrimSuffix(Conf.FileTree.DocCreateSavePath, "/")
Conf.FileTree.DocCreateSavePath = strings.TrimSpace(Conf.FileTree.DocCreateSavePath)
}
if nil == Conf.Tag {
Conf.Tag = conf.NewTag()
}
if nil == Conf.Editor {
Conf.Editor = conf.NewEditor()
}
if 1 > len(Conf.Editor.Emoji) {
Conf.Editor.Emoji = []string{}
}
if 1 > Conf.Editor.BlockRefDynamicAnchorTextMaxLen {
Conf.Editor.BlockRefDynamicAnchorTextMaxLen = 64
}
if 5120 < Conf.Editor.BlockRefDynamicAnchorTextMaxLen {
Conf.Editor.BlockRefDynamicAnchorTextMaxLen = 5120
}
if nil == Conf.Export {
Conf.Export = conf.NewExport()
}
if 0 == Conf.Export.BlockRefMode || 1 == Conf.Export.BlockRefMode {
// 废弃导出选项引用块转换为原始块和引述块 https://github.com/siyuan-note/siyuan/issues/3155
Conf.Export.BlockRefMode = 4 // 改为脚注
}
if "" == Conf.Export.PandocBin {
Conf.Export.PandocBin = util.PandocBinPath
}
if 9 > Conf.Editor.FontSize || 72 < Conf.Editor.FontSize {
Conf.Editor.FontSize = 16
}
if "" == Conf.Editor.PlantUMLServePath {
Conf.Editor.PlantUMLServePath = "https://www.plantuml.com/plantuml/svg/~1"
}
if nil == Conf.Graph || nil == Conf.Graph.Local || nil == Conf.Graph.Global {
Conf.Graph = conf.NewGraph()
}
if nil == Conf.System {
Conf.System = conf.NewSystem()
Conf.OpenHelp = true
} else {
2023-03-07 09:49:36 +08:00
if 0 < semver.Compare("v"+util.Ver, "v"+Conf.System.KernelVersion) {
logging.LogInfof("upgraded from version [%s] to [%s]", Conf.System.KernelVersion, util.Ver)
2023-03-10 16:45:30 +08:00
} else if 0 > semver.Compare("v"+util.Ver, "v"+Conf.System.KernelVersion) {
2023-03-07 09:49:36 +08:00
logging.LogInfof("downgraded from version [%s] to [%s]", Conf.System.KernelVersion, util.Ver)
}
Conf.System.KernelVersion = util.Ver
Conf.System.IsInsider = util.IsInsider
task.AppendTask(task.UpgradeUserGuide, upgradeUserGuide)
}
if nil == Conf.System.NetworkProxy {
Conf.System.NetworkProxy = &conf.NetworkProxy{}
}
if "" == Conf.System.ID {
Conf.System.ID = util.GetDeviceID()
}
if util.ContainerStd == util.Container {
Conf.System.ID = util.GetDeviceID()
}
Conf.System.AppDir = util.WorkingDir
Conf.System.ConfDir = util.ConfDir
Conf.System.HomeDir = util.HomeDir
Conf.System.WorkspaceDir = util.WorkspaceDir
Conf.System.DataDir = util.DataDir
Conf.System.Container = util.Container
Conf.System.IsMicrosoftStore = util.ISMicrosoftStore
if util.ISMicrosoftStore {
logging.LogInfof("using Microsoft Store edition")
}
Conf.System.OS = runtime.GOOS
2023-03-25 17:23:22 +08:00
Conf.System.OSPlatform = util.GetOSPlatform()
if "" != Conf.UserData {
Conf.User = loadUserFromConf()
}
if nil == Conf.Account {
Conf.Account = conf.NewAccount()
}
if nil == Conf.Sync {
Conf.Sync = conf.NewSync()
}
if 0 == Conf.Sync.Mode {
Conf.Sync.Mode = 1
}
if nil == Conf.Sync.S3 {
Conf.Sync.S3 = &conf.S3{}
}
Conf.Sync.S3.Endpoint = util.NormalizeEndpoint(Conf.Sync.S3.Endpoint)
Conf.Sync.S3.Timeout = util.NormalizeTimeout(Conf.Sync.S3.Timeout)
if nil == Conf.Sync.WebDAV {
Conf.Sync.WebDAV = &conf.WebDAV{}
}
Conf.Sync.WebDAV.Endpoint = util.NormalizeEndpoint(Conf.Sync.WebDAV.Endpoint)
Conf.Sync.WebDAV.Timeout = util.NormalizeTimeout(Conf.Sync.WebDAV.Timeout)
if nil == Conf.Api {
Conf.Api = conf.NewAPI()
}
if nil == Conf.Repo {
Conf.Repo = conf.NewRepo()
}
if 1440 < Conf.Editor.GenerateHistoryInterval {
Conf.Editor.GenerateHistoryInterval = 1440
}
if 1 > Conf.Editor.HistoryRetentionDays {
Conf.Editor.HistoryRetentionDays = 7
}
if 48 > Conf.Editor.DynamicLoadBlocks {
Conf.Editor.DynamicLoadBlocks = 48
}
if 1024 < Conf.Editor.DynamicLoadBlocks {
Conf.Editor.DynamicLoadBlocks = 1024
}
if 0 > Conf.Editor.BacklinkExpandCount {
Conf.Editor.BacklinkExpandCount = 0
}
if 0 > Conf.Editor.BackmentionExpandCount {
Conf.Editor.BackmentionExpandCount = 0
}
if nil == Conf.Search {
Conf.Search = conf.NewSearch()
}
if 1 > Conf.Search.Limit {
Conf.Search.Limit = 64
}
if 32 > Conf.Search.Limit {
Conf.Search.Limit = 32
}
if 1 > Conf.Search.BacklinkMentionKeywordsLimit {
Conf.Search.BacklinkMentionKeywordsLimit = 512
}
if nil == Conf.Stat {
Conf.Stat = conf.NewStat()
}
if nil == Conf.Flashcard {
Conf.Flashcard = conf.NewFlashcard()
}
if 0 > Conf.Flashcard.NewCardLimit {
Conf.Flashcard.NewCardLimit = 20
}
if 0 > Conf.Flashcard.ReviewCardLimit {
Conf.Flashcard.ReviewCardLimit = 200
}
if nil == Conf.AI {
Conf.AI = conf.NewAI()
}
if "" != Conf.AI.OpenAI.APIKey {
logging.LogInfof("OpenAI API enabled\n"+
" baseURL=%s\n"+
" timeout=%ds\n"+
" proxy=%s\n"+
" maxTokens=%d",
Conf.AI.OpenAI.APIBaseURL, Conf.AI.OpenAI.APITimeout, Conf.AI.OpenAI.APIProxy, Conf.AI.OpenAI.APIMaxTokens)
}
Conf.ReadOnly = util.ReadOnly
if "" != util.AccessAuthCode {
Conf.AccessAuthCode = util.AccessAuthCode
}
Conf.LocalIPs = util.GetLocalIPs()
Conf.Save()
logging.SetLogLevel(Conf.LogLevel)
if Conf.System.UploadErrLog {
logging.LogInfof("user has enabled [Automatically upload error messages and diagnostic data]")
sentry.Init(sentry.ClientOptions{
Dsn: "https://bdff135f14654ae58a054adeceb2c308@o1173696.ingest.sentry.io/6269178",
Release: util.Ver,
Environment: util.Mode,
})
}
if Conf.System.DisableGoogleAnalytics {
logging.LogInfof("user has disabled [Google Analytics]")
}
util.SetNetworkProxy(Conf.System.NetworkProxy.String())
}
func initLang() {
p := filepath.Join(util.WorkingDir, "appearance", "langs")
dir, err := os.Open(p)
if nil != err {
logging.LogErrorf("open language configuration folder [%s] failed: %s", p, err)
util.ReportFileSysFatalError(err)
return
}
defer dir.Close()
langNames, err := dir.Readdirnames(-1)
if nil != err {
logging.LogErrorf("list language configuration folder [%s] failed: %s", p, err)
util.ReportFileSysFatalError(err)
return
}
for _, langName := range langNames {
jsonPath := filepath.Join(p, langName)
data, err := os.ReadFile(jsonPath)
if nil != err {
logging.LogErrorf("read language configuration [%s] failed: %s", jsonPath, err)
continue
}
langMap := map[string]interface{}{}
if err := gulu.JSON.UnmarshalJSON(data, &langMap); nil != err {
logging.LogErrorf("parse language configuration failed [%s] failed: %s", jsonPath, err)
continue
}
kernelMap := map[int]string{}
label := langMap["_label"].(string)
kernelLangs := langMap["_kernel"].(map[string]interface{})
for k, v := range kernelLangs {
num, err := strconv.Atoi(k)
if nil != err {
logging.LogErrorf("parse language configuration [%s] item [%d] failed [%s] failed: %s", p, num, err)
continue
}
kernelMap[num] = v.(string)
}
kernelMap[-1] = label
name := langName[:strings.LastIndex(langName, ".")]
util.Langs[name] = kernelMap
util.TimeLangs[name] = langMap["_time"].(map[string]interface{})
util.TaskActionLangs[name] = langMap["_taskAction"].(map[string]interface{})
}
}
func loadLangs() (ret []*conf.Lang) {
for name, langMap := range util.Langs {
lang := &conf.Lang{Label: langMap[-1], Name: name}
ret = append(ret, lang)
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].Name < ret[j].Name
})
return
}
var exitLock = sync.Mutex{}
// Close 退出内核进程.
//
// force是否不执行同步过程而直接退出
// execInstallPkg是否执行新版本安装包
// 0默认按照设置项 System.DownloadInstallPkg 检查并推送提示
// 1不执行新版本安装
// 2执行新版本安装
func Close(force bool, execInstallPkg int) (exitCode int) {
exitLock.Lock()
defer exitLock.Unlock()
util.IsExiting = true
logging.LogInfof("exiting kernel [force=%v, execInstallPkg=%d]", force, execInstallPkg)
util.PushMsg(Conf.Language(95), 10000*60)
WaitForWritingFiles()
if !force {
if Conf.Sync.Enabled && 3 != Conf.Sync.Mode &&
((IsSubscriber() && conf.ProviderSiYuan == Conf.Sync.Provider) || conf.ProviderSiYuan != Conf.Sync.Provider) {
2023-04-04 10:37:18 +08:00
syncData(true, false)
if 0 != ExitSyncSucc {
exitCode = 1
return
}
}
}
waitSecondForExecInstallPkg := false
if !skipNewVerInstallPkg() {
newVerInstallPkgPath := getNewVerInstallPkgPath()
if "" != newVerInstallPkgPath {
if 0 == execInstallPkg { // 新版本安装包已经准备就绪
exitCode = 2
logging.LogInfof("the new version install pkg is ready [%s], waiting for the user's next instruction", newVerInstallPkgPath)
return
} else if 2 == execInstallPkg { // 执行新版本安装
waitSecondForExecInstallPkg = true
go execNewVerInstallPkg(newVerInstallPkgPath)
}
}
}
Conf.Close()
sql.CloseDatabase()
treenode.SaveBlockTree(false)
SaveAssetsTexts()
clearWorkspaceTemp()
clearCorruptedNotebooks()
clearPortJSON()
util.UnlockWorkspace()
go func() {
time.Sleep(500 * time.Millisecond)
if waitSecondForExecInstallPkg {
util.PushMsg(Conf.Language(130), 1000*5)
// 桌面端退出拉起更新安装时有时需要重启两次 https://github.com/siyuan-note/siyuan/issues/6544
// 这里多等待一段时间,等待安装程序启动
time.Sleep(4 * time.Second)
}
logging.LogInfof("exited kernel")
util.WebSocketServer.Close()
os.Exit(logging.ExitCodeOk)
}()
return
}
var CustomEmojis = sync.Map{}
func NewLute() (ret *lute.Lute) {
ret = util.NewLute()
ret.SetCodeSyntaxHighlightLineNum(Conf.Editor.CodeSyntaxHighlightLineNum)
ret.SetChineseParagraphBeginningSpace(Conf.Export.ParagraphBeginningSpace)
ret.SetProtyleMarkNetImg(Conf.Editor.DisplayNetImgMark)
ret.SetSpellcheck(Conf.Editor.Spellcheck)
customEmojiMap := map[string]string{}
CustomEmojis.Range(func(key, value interface{}) bool {
customEmojiMap[key.(string)] = value.(string)
return true
})
ret.PutEmojis(customEmojiMap)
return
}
var confSaveLock = sync.Mutex{}
func (conf *AppConf) Save() {
if util.ReadOnly {
return
}
confSaveLock.Lock()
confSaveLock.Unlock()
newData, _ := gulu.JSON.MarshalIndentJSON(Conf, "", " ")
confPath := filepath.Join(util.ConfDir, "conf.json")
oldData, err := filelock.ReadFile(confPath)
if nil != err {
conf.save0(newData)
return
}
if bytes.Equal(newData, oldData) {
return
}
conf.save0(newData)
}
func (conf *AppConf) save0(data []byte) {
confPath := filepath.Join(util.ConfDir, "conf.json")
if err := filelock.WriteFile(confPath, data); nil != err {
logging.LogErrorf("write conf [%s] failed: %s", confPath, err)
util.ReportFileSysFatalError(err)
return
}
}
func (conf *AppConf) Close() {
conf.Save()
}
func (conf *AppConf) Box(boxID string) *Box {
for _, box := range conf.GetOpenedBoxes() {
if box.ID == boxID {
return box
}
}
return nil
}
func (conf *AppConf) BoxNames(boxIDs []string) (ret map[string]string) {
ret = map[string]string{}
boxes := conf.GetOpenedBoxes()
for _, boxID := range boxIDs {
for _, box := range boxes {
if box.ID == boxID {
ret[boxID] = box.Name
break
}
}
}
return
}
func (conf *AppConf) GetBoxes() (ret []*Box) {
ret = []*Box{}
notebooks, err := ListNotebooks()
if nil != err {
return
}
for _, notebook := range notebooks {
id := notebook.ID
name := notebook.Name
closed := notebook.Closed
box := &Box{ID: id, Name: name, Closed: closed}
ret = append(ret, box)
}
return
}
func (conf *AppConf) GetOpenedBoxes() (ret []*Box) {
ret = []*Box{}
notebooks, err := ListNotebooks()
if nil != err {
return
}
for _, notebook := range notebooks {
if !notebook.Closed {
ret = append(ret, notebook)
}
}
return
}
func (conf *AppConf) GetClosedBoxes() (ret []*Box) {
ret = []*Box{}
notebooks, err := ListNotebooks()
if nil != err {
return
}
for _, notebook := range notebooks {
if notebook.Closed {
ret = append(ret, notebook)
}
}
return
}
func (conf *AppConf) Language(num int) (ret string) {
ret = util.Langs[conf.Lang][num]
if "" != ret {
return
}
ret = util.Langs["en_US"][num]
return
}
func InitBoxes() {
initialized := false
2022-08-31 10:50:17 +08:00
if 1 > treenode.CountBlocks() {
if gulu.File.IsExist(util.BlockTreePath) {
util.IncBootProgress(20, Conf.Language(91))
go func() {
for i := 0; i < 40; i++ {
2022-07-19 01:05:05 +08:00
util.RandomSleep(50, 100)
util.IncBootProgress(1, Conf.Language(91))
}
}()
2022-06-30 22:40:43 +08:00
treenode.InitBlockTree(false)
2022-06-30 22:40:43 +08:00
initialized = true
}
} else { // 大于 1 的话说明在同步阶段已经加载过了
initialized = true
}
for _, box := range Conf.GetOpenedBoxes() {
box.UpdateHistoryGenerated() // 初始化历史生成时间为当前时间
if !initialized {
index(box.ID)
}
}
if !initialized {
treenode.SaveBlockTree(true)
}
var dbSize string
if dbFile, err := os.Stat(util.DBPath); nil == err {
dbSize = humanize.Bytes(uint64(dbFile.Size()))
}
2022-08-31 10:50:17 +08:00
logging.LogInfof("database size [%s], tree/block count [%d/%d]", dbSize, treenode.CountTrees(), treenode.CountBlocks())
}
func IsSubscriber() bool {
return nil != Conf.User && (-1 == Conf.User.UserSiYuanProExpireTime || 0 < Conf.User.UserSiYuanProExpireTime) && 0 == Conf.User.UserSiYuanSubscriptionStatus
}
const (
MaskedUserData = ""
MaskedAccessAuthCode = "*******"
)
func GetMaskedConf() (ret *AppConf, err error) {
// 脱敏处理
data, err := gulu.JSON.MarshalIndentJSON(Conf, "", " ")
if nil != err {
logging.LogErrorf("marshal conf failed: %s", err)
return
}
ret = &AppConf{}
if err = gulu.JSON.UnmarshalJSON(data, ret); nil != err {
logging.LogErrorf("unmarshal conf failed: %s", err)
return
}
ret.UserData = MaskedUserData
if "" != ret.AccessAuthCode {
ret.AccessAuthCode = MaskedAccessAuthCode
}
return
}
func clearPortJSON() {
pid := fmt.Sprintf("%d", os.Getpid())
portJSON := filepath.Join(util.HomeDir, ".config", "siyuan", "port.json")
pidPorts := map[string]string{}
var data []byte
var err error
if gulu.File.IsExist(portJSON) {
data, err = os.ReadFile(portJSON)
if nil != err {
logging.LogWarnf("read port.json failed: %s", err)
} else {
if err = gulu.JSON.UnmarshalJSON(data, &pidPorts); nil != err {
logging.LogWarnf("unmarshal port.json failed: %s", err)
}
}
}
delete(pidPorts, pid)
if data, err = gulu.JSON.MarshalIndentJSON(pidPorts, "", " "); nil != err {
logging.LogWarnf("marshal port.json failed: %s", err)
} else {
if err = os.WriteFile(portJSON, data, 0644); nil != err {
logging.LogWarnf("write port.json failed: %s", err)
}
}
}
func clearCorruptedNotebooks() {
// 数据同步时展开文档树操作可能导致数据丢失 https://github.com/siyuan-note/siyuan/issues/7129
dirs, err := os.ReadDir(util.DataDir)
if nil != err {
logging.LogErrorf("read dir [%s] failed: %s", util.DataDir, err)
return
}
for _, dir := range dirs {
if util.IsReservedFilename(dir.Name()) {
continue
}
if !dir.IsDir() {
continue
}
if !ast.IsNodeIDPattern(dir.Name()) {
continue
}
boxDirPath := filepath.Join(util.DataDir, dir.Name())
boxConfPath := filepath.Join(boxDirPath, ".siyuan", "conf.json")
if !gulu.File.IsExist(boxConfPath) {
logging.LogWarnf("found a corrupted box [%s]", boxDirPath)
continue
}
}
}
func clearWorkspaceTemp() {
os.RemoveAll(filepath.Join(util.TempDir, "bazaar"))
os.RemoveAll(filepath.Join(util.TempDir, "export"))
os.RemoveAll(filepath.Join(util.TempDir, "import"))
os.RemoveAll(filepath.Join(util.TempDir, "repo"))
os.RemoveAll(filepath.Join(util.TempDir, "os"))
os.RemoveAll(filepath.Join(util.TempDir, "blocktree.msgpack")) // v2.7.2 前旧版的块数数据
// 退出时自动删除超过 7 天的安装包 https://github.com/siyuan-note/siyuan/issues/6128
install := filepath.Join(util.TempDir, "install")
if gulu.File.IsDir(install) {
monthAgo := time.Now().Add(-time.Hour * 24 * 7)
entries, err := os.ReadDir(install)
if nil != err {
logging.LogErrorf("read dir [%s] failed: %s", install, err)
} else {
for _, entry := range entries {
info, _ := entry.Info()
if nil != info && !info.IsDir() && info.ModTime().Before(monthAgo) {
if err = os.RemoveAll(filepath.Join(install, entry.Name())); nil != err {
logging.LogErrorf("remove old install pkg [%s] failed: %s", filepath.Join(install, entry.Name()), err)
}
}
}
}
}
tmps, err := filepath.Glob(filepath.Join(util.TempDir, "*.tmp"))
if nil != err {
logging.LogErrorf("glob temp files failed: %s", err)
}
for _, tmp := range tmps {
if err = os.RemoveAll(tmp); nil != err {
logging.LogErrorf("remove temp file [%s] failed: %s", tmp, err)
} else {
logging.LogInfof("removed temp file [%s]", tmp)
}
}
tmps, err = filepath.Glob(filepath.Join(util.DataDir, ".siyuan", "*.tmp"))
if nil != err {
logging.LogErrorf("glob temp files failed: %s", err)
}
for _, tmp := range tmps {
if err = os.RemoveAll(tmp); nil != err {
logging.LogErrorf("remove temp file [%s] failed: %s", tmp, err)
} else {
logging.LogInfof("removed temp file [%s]", tmp)
}
}
// 老版本遗留文件清理
os.RemoveAll(filepath.Join(util.DataDir, "assets", ".siyuan", "assets.json"))
2023-02-18 11:05:08 +08:00
os.RemoveAll(filepath.Join(util.DataDir, ".siyuan", "history"))
os.RemoveAll(filepath.Join(util.WorkspaceDir, "backup"))
os.RemoveAll(filepath.Join(util.WorkspaceDir, "sync"))
logging.LogInfof("cleared workspace temp")
}
func upgradeUserGuide() {
defer logging.Recover()
dirs, err := os.ReadDir(util.DataDir)
if nil != err {
logging.LogErrorf("read dir [%s] failed: %s", util.DataDir, err)
return
}
for _, dir := range dirs {
if !IsUserGuide(dir.Name()) {
continue
}
boxID := dir.Name()
boxDirPath := filepath.Join(util.DataDir, boxID)
boxConf := conf.NewBoxConf()
boxConfPath := filepath.Join(boxDirPath, ".siyuan", "conf.json")
if !gulu.File.IsExist(boxConfPath) {
logging.LogWarnf("found a corrupted box [%s]", boxDirPath)
continue
}
data, readErr := filelock.ReadFile(boxConfPath)
if nil != readErr {
logging.LogErrorf("read box conf [%s] failed: %s", boxConfPath, readErr)
continue
}
if readErr = gulu.JSON.UnmarshalJSON(data, boxConf); nil != readErr {
logging.LogErrorf("parse box conf [%s] failed: %s", boxConfPath, readErr)
continue
}
if boxConf.Closed {
continue
}
unindex(boxID)
if err = filelock.Remove(boxDirPath); nil != err {
return
}
p := filepath.Join(util.WorkingDir, "guide", boxID)
if err = filelock.Copy(p, boxDirPath); nil != err {
return
}
index(boxID)
}
}