diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 88cdff822..7a89b446b 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1,4 +1,9 @@ { + "cardShowAnswer": "Show Answer", + "cardRatingAgain": "Again", + "cardRatingHard": "Hard", + "cardRatingGood": "Good", + "cardRatingEasy": "Easy", "pdfIsLoading": "PDF is loading, please try again later", "addToDeck": "Add to Deck...", "quickMakeCard": "Quick make card", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index c8deb17d0..355d504cd 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1,5 +1,10 @@ { "pdfIsLoading": "El PDF se está cargando, inténtalo de nuevo más tarde", + "cardShowAnswer": "Afficher la réponse", + "cardRatingAgain": "Otra vez", + "cardRatingHard": "Difícil", + "cardRatingGood": "Bueno", + "cardRatingEasy": "Fácil", "addToDeck": "Agregar a la plataforma...", "quickMakeCard": "Tarjeta de creación rápida", "allAttrs": "Todos los nombres de atributos y valores de atributos", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index 8593f3840..cc0092c78 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1,4 +1,9 @@ { + "cardShowAnswer": "Afficher la réponse", + "cardRatingAgain": "Encore", + "cardRatingHard": "Difficile", + "cardRatingGood": "Bien", + "cardRatingEasy": "Facile", "pdfIsLoading": "Le PDF est en cours de chargement, veuillez réessayer plus tard", "addToDeck": "Ajouter au deck...", "quickMakeCard": "Carte de création rapide", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index d5f6d5121..787762a05 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1,5 +1,10 @@ { "pdfIsLoading": "PDF 正在加載中,請稍後再試", + "cardShowAnswer": "顯示答案", + "cardRatingAgain": "重來", + "cardRatingHard": "困難", + "cardRatingGood": "一般", + "cardRatingEasy": "輕鬆", "addToDeck": "添加到卡包...", "quickMakeCard": "快速制卡", "allAttrs": "所有屬性名和屬性值", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 90a8896cf..41c29c9f5 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1,5 +1,10 @@ { "pdfIsLoading": "PDF 正在加载中,请稍后再试", + "cardShowAnswer": "显示答案", + "cardRatingAgain": "重来", + "cardRatingHard": "困难", + "cardRatingGood": "一般", + "cardRatingEasy": "轻松", "addToDeck": "添加到卡包...", "quickMakeCard": "快速制卡", "allAttrs": "所有属性名和属性值", diff --git a/app/src/assets/scss/business/_card.scss b/app/src/assets/scss/business/_card.scss index 815511628..d8084680c 100644 --- a/app/src/assets/scss/business/_card.scss +++ b/app/src/assets/scss/business/_card.scss @@ -24,7 +24,7 @@ width: 90%; & > div { - font-size: 46px; + font-size: 32px; display: block; line-height: 46px; margin-bottom: 4px; diff --git a/app/src/card/openCard.ts b/app/src/card/openCard.ts index 569bef18f..7d314ea19 100644 --- a/app/src/card/openCard.ts +++ b/app/src/card/openCard.ts @@ -54,35 +54,35 @@ export const openCardByData = (cardsData: ICard[], html = "") => { ${window.siyuan.languages.noDueCard}
- +
diff --git a/app/src/mobile/util/initFramework.ts b/app/src/mobile/util/initFramework.ts index 6497ee3f7..dffcbfca4 100644 --- a/app/src/mobile/util/initFramework.ts +++ b/app/src/mobile/util/initFramework.ts @@ -128,7 +128,7 @@ export const initFramework = () => { closePanel(); }); initEditorName(); - if (window.siyuan.config.newbie) { + if (window.siyuan.config.openHelp) { mountHelp(); } const transactionTipElement = document.getElementById("transactionTip"); diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index a4776a015..b8baeb851 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -413,7 +413,7 @@ declare interface IConfig { api: { token: string } - newbie: boolean + openHelp: boolean system: { networkProxy: { host: string diff --git a/app/src/util/onGetConfig.ts b/app/src/util/onGetConfig.ts index db7b34fcb..921244ab2 100644 --- a/app/src/util/onGetConfig.ts +++ b/app/src/util/onGetConfig.ts @@ -176,7 +176,7 @@ export const onGetConfig = (isStart: boolean) => { resizeDrag(); }, 200); }); - if (window.siyuan.config.newbie) { + if (window.siyuan.config.openHelp) { mountHelp(); } addGA(); diff --git a/kernel/api/extension.go b/kernel/api/extension.go index c7d0a4810..7b6124b99 100644 --- a/kernel/api/extension.go +++ b/kernel/api/extension.go @@ -30,6 +30,7 @@ import ( "github.com/88250/lute" "github.com/88250/lute/ast" "github.com/88250/lute/parse" + "github.com/gabriel-vasile/mimetype" "github.com/gin-gonic/gin" "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" @@ -80,7 +81,7 @@ func extensionCopy(c *gin.Context) { continue } fName := path.Base(u.Path) - fName = util.FilterUploadFileName(fName) + f, err := file[0].Open() if nil != err { ret.Code = -1 @@ -96,10 +97,19 @@ func extensionCopy(c *gin.Context) { } ext := path.Ext(fName) - fName = fName[0 : len(fName)-len(ext)] + originalExt := ext + if "" == ext || strings.Contains(ext, "!") { + // 改进浏览器剪藏扩展转换本地图片后缀 https://github.com/siyuan-note/siyuan/issues/7467 + if mtype := mimetype.Detect(data); nil != mtype { + ext = mtype.Extension() + } + } if "" == ext && bytes.HasPrefix(data, []byte("")) { ext = ".svg" } + + fName = fName[0 : len(fName)-len(originalExt)] + fName = util.FilterUploadFileName(fName) fName = fName + "-" + ast.NewNodeID() + ext writePath := filepath.Join(assets, fName) if err = filelock.WriteFile(writePath, data); nil != err { diff --git a/kernel/api/format.go b/kernel/api/format.go index 6b8076941..a7eb4c0ac 100644 --- a/kernel/api/format.go +++ b/kernel/api/format.go @@ -35,7 +35,11 @@ func netImg2LocalAssets(c *gin.Context) { } id := arg["id"].(string) - err := model.NetImg2LocalAssets(id) + var url string + if urlArg := arg["url"]; nil != urlArg { + url = urlArg.(string) + } + err := model.NetImg2LocalAssets(id, url) if nil != err { ret.Code = -1 ret.Msg = err.Error() diff --git a/kernel/go.mod b/kernel/go.mod index 22d953779..f998326e3 100644 --- a/kernel/go.mod +++ b/kernel/go.mod @@ -47,7 +47,7 @@ require ( github.com/siyuan-note/filelock v0.0.0-20230223100551-200cbe1cf84e github.com/siyuan-note/httpclient v0.0.0-20230223101139-409ed0b4c5ff github.com/siyuan-note/logging v0.0.0-20230223101545-ec2cbf198ffb - github.com/siyuan-note/riff v0.0.0-20221228031102-17d458a1217b + github.com/siyuan-note/riff v0.0.0-20230224070227-4514ccc3e496 github.com/steambap/captcha v1.4.1 github.com/studio-b12/gowebdav v0.0.0-20230203202212-3282f94193f2 github.com/vmihailenco/msgpack/v5 v5.3.5 diff --git a/kernel/go.sum b/kernel/go.sum index b6adf3ddd..08b9c72b3 100644 --- a/kernel/go.sum +++ b/kernel/go.sum @@ -10,8 +10,6 @@ github.com/88250/gulu v1.2.3-0.20230223100136-26e5f16ac3c0 h1:hZn2F/kNKcxoK41Jhf github.com/88250/gulu v1.2.3-0.20230223100136-26e5f16ac3c0/go.mod h1:pTWnjt+6qUqNnP9xltswsJxgCBVu3C7eW09u48LWX0k= github.com/88250/lute v1.7.6-0.20230223100349-d4c62da413ce h1:PGos/Sz/SRVDPzToUgn/SBttEsAO5livLUWzoI+/bZ4= github.com/88250/lute v1.7.6-0.20230223100349-d4c62da413ce/go.mod h1:+wUqx/1kdFDbWtxn9LYJlaCOAeol2pjSO6w+WJTVQsg= -github.com/88250/pdfcpu v0.3.14-0.20230223050947-68dec81c7661 h1:s8YOfk7TpajM8SBivP0ReIHmNfMQu20hWgEBc98D14w= -github.com/88250/pdfcpu v0.3.14-0.20230223050947-68dec81c7661/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4= github.com/88250/pdfcpu v0.3.14-0.20230224021324-e51076eb6390 h1:q2AR33VoQ87WYtvZ4pEvwj5gZkv22HK/yMlPWwF1oyc= github.com/88250/pdfcpu v0.3.14-0.20230224021324-e51076eb6390/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4= github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY= @@ -288,8 +286,8 @@ github.com/siyuan-note/httpclient v0.0.0-20230223101139-409ed0b4c5ff h1:3G48J/tG github.com/siyuan-note/httpclient v0.0.0-20230223101139-409ed0b4c5ff/go.mod h1:/fjYEiYPN2ZNR2zVTopobwzo3rOychV2qbsutxiV0jI= github.com/siyuan-note/logging v0.0.0-20230223101545-ec2cbf198ffb h1:qzz7ZQw7/tHJd1IST+8UymXFF8RacokMLD7VZgyS+ww= github.com/siyuan-note/logging v0.0.0-20230223101545-ec2cbf198ffb/go.mod h1:6mRFtAAvYPn3cDzqvyv+t8BVPGqpONDMMb5ywOhY1D4= -github.com/siyuan-note/riff v0.0.0-20221228031102-17d458a1217b h1:JDpKOdiyocNsgKFfrF3mB7UoBJz4qcHBUKBig78kVjc= -github.com/siyuan-note/riff v0.0.0-20221228031102-17d458a1217b/go.mod h1:WnNt0JPjfXp2fjAgbF9rS5W7JC2W0YVcaVmLXIeYF8A= +github.com/siyuan-note/riff v0.0.0-20230224070227-4514ccc3e496 h1:6u9vlE4EhRja4abccUPGNmG+aMBm/D+5lVomkoYuSmo= +github.com/siyuan-note/riff v0.0.0-20230224070227-4514ccc3e496/go.mod h1:XJtLlKCr8cZE+lzykM4edHHih92M9M50UNw/nDLYRN8= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.7 h1:I6tZjLXD2Q1kjvNbIzB1wvQBsXmKXiVrhpRE8ZjP5jY= diff --git a/kernel/model/assets.go b/kernel/model/assets.go index cc415cc57..57632afde 100644 --- a/kernel/model/assets.go +++ b/kernel/model/assets.go @@ -70,7 +70,7 @@ func DocImageAssets(rootID string) (ret []string, err error) { return } -func NetImg2LocalAssets(rootID string) (err error) { +func NetImg2LocalAssets(rootID, originalURL string) (err error) { tree, err := loadTreeByBlockID(rootID) if nil != err { return @@ -138,6 +138,7 @@ func NetImg2LocalAssets(rootID string) (err error) { } util.PushUpdateMsg(msgId, fmt.Sprintf(Conf.Language(119), u), 15000) request := httpclient.NewBrowserRequest() + request.SetHeader("Referer", originalURL) // 改进浏览器剪藏扩展转换本地图片成功率 https://github.com/siyuan-note/siyuan/issues/7464 resp, reqErr := request.Get(u) if nil != reqErr { logging.LogErrorf("download net img [%s] failed: %s", u, reqErr) diff --git a/kernel/model/conf.go b/kernel/model/conf.go index eae65a00e..3aee764c8 100644 --- a/kernel/model/conf.go +++ b/kernel/model/conf.go @@ -70,7 +70,7 @@ type AppConf struct { Stat *conf.Stat `json:"stat"` // 统计 Api *conf.API `json:"api"` // API Repo *conf.Repo `json:"repo"` // 数据仓库 - Newbie bool `json:"newbie"` // 是否是安装后第一次启动 + OpenHelp bool `json:"openHelp"` // 启动后是否需要打开用户指南 } func InitConf() { @@ -211,7 +211,9 @@ func InitConf() { } if nil == Conf.System { Conf.System = conf.NewSystem() + Conf.OpenHelp = true } else { + Conf.OpenHelp = Conf.System.KernelVersion != util.Ver Conf.System.KernelVersion = util.Ver Conf.System.IsInsider = util.IsInsider } @@ -237,7 +239,6 @@ func InitConf() { } Conf.System.OS = runtime.GOOS Conf.System.OSPlatform, _ = util.GetOSPlatform() - Conf.Newbie = util.IsNewbie if "" != Conf.UserData { Conf.User = loadUserFromConf() diff --git a/kernel/model/file.go b/kernel/model/file.go index 925e5581c..5833b3c2c 100644 --- a/kernel/model/file.go +++ b/kernel/model/file.go @@ -19,7 +19,6 @@ package model import ( "errors" "fmt" - "math" "os" "path" "path/filepath" @@ -93,35 +92,10 @@ func (box *Box) docFromFileInfo(fileInfo *FileInfo, ial map[string]string) (ret } ret.Mtime = mTime.Unix() - ret.HMtime = HumanizeTime(mTime) + ret.HMtime = util.HumanizeTime(mTime, Conf.Lang) return } -func HumanizeTime(then time.Time) string { - labels := util.TimeLangs[Conf.Lang] - - defaultMagnitudes := []humanize.RelTimeMagnitude{ - {time.Second, labels["now"].(string), time.Second}, - {2 * time.Second, labels["1s"].(string), 1}, - {time.Minute, labels["xs"].(string), time.Second}, - {2 * time.Minute, labels["1m"].(string), 1}, - {time.Hour, labels["xm"].(string), time.Minute}, - {2 * time.Hour, labels["1h"].(string), 1}, - {humanize.Day, labels["xh"].(string), time.Hour}, - {2 * humanize.Day, labels["1d"].(string), 1}, - {humanize.Week, labels["xd"].(string), humanize.Day}, - {2 * humanize.Week, labels["1w"].(string), 1}, - {humanize.Month, labels["xw"].(string), humanize.Week}, - {2 * humanize.Month, labels["1M"].(string), 1}, - {humanize.Year, labels["xM"].(string), humanize.Month}, - {18 * humanize.Month, labels["1y"].(string), 1}, - {2 * humanize.Year, labels["2y"].(string), 1}, - {humanize.LongTime, labels["xy"].(string), humanize.Year}, - {math.MaxInt64, labels["max"].(string), 1}, - } - return humanize.CustomRelTime(then, time.Now(), labels["albl"].(string), labels["blbl"].(string), defaultMagnitudes) -} - func (box *Box) docIAL(p string) (ret map[string]string) { name := strings.ToLower(filepath.Base(p)) if !strings.HasSuffix(name, ".sy") { diff --git a/kernel/model/flashcard.go b/kernel/model/flashcard.go index bf6e1214a..6b0a71579 100644 --- a/kernel/model/flashcard.go +++ b/kernel/model/flashcard.go @@ -29,7 +29,6 @@ import ( "github.com/88250/gulu" "github.com/88250/lute/ast" "github.com/88250/lute/parse" - "github.com/dustin/go-humanize" "github.com/siyuan-note/logging" "github.com/siyuan-note/riff" "github.com/siyuan-note/siyuan/kernel/cache" @@ -156,6 +155,9 @@ func GetFlashcards(deckID string, page int) (blocks []*Block, total, pageCount i return } +// reviewCardCache 用于复习时缓存卡片,以便支持撤销。 +var reviewCardCache = map[string]riff.Card{} + func ReviewFlashcard(deckID string, blockID string, rating riff.Rating) (err error) { deckLock.Lock() defer deckLock.Unlock() @@ -166,17 +168,39 @@ func ReviewFlashcard(deckID string, blockID string, rating riff.Rating) (err err } deck := Decks[deckID] + card := deck.GetCard(blockID) + if nil == card { + logging.LogErrorf("card not found [%s]", blockID) + return + } + + if cachedCard := reviewCardCache[card.ID()]; nil != cachedCard { + // 命中缓存说明这张卡片已经复习过了,这次调用复习是撤销后再次复习 + // 将缓存的卡片重新覆盖回卡包中,以恢复最开始复习前的状态 + deck.SetCard(cachedCard) + } else { + // 首次复习该卡片,将卡片缓存以便后续支持撤销后再次复习 + reviewCardCache[card.ID()] = card + } + deck.Review(blockID, rating) err = deck.Save() if nil != err { logging.LogErrorf("save deck [%s] failed: %s", deckID, err) return } + + dueCards := getDueFlashcards(deckID) + if 1 > len(dueCards) { + // 该卡包中没有待复习的卡片了,说明最后一张卡片已经复习完了,清空撤销缓存 + reviewCardCache = map[string]riff.Card{} + } return } type Flashcard struct { DeckID string `json:"deckID"` + CardID string `json:"cardID"` BlockID string `json:"blockID"` NextDues map[riff.Rating]string `json:"nextDues"` } @@ -207,11 +231,12 @@ func GetTreeDueFlashcards(rootID string) (ret []*Flashcard, err error) { nextDues := map[riff.Rating]string{} for rating, due := range card.NextDues() { - nextDues[rating] = strings.TrimSpace(humanize.RelTime(due, now, "", "")) + nextDues[rating] = strings.TrimSpace(util.HumanizeRelTime(due, now, Conf.Lang)) } ret = append(ret, &Flashcard{ DeckID: builtinDeckID, + CardID: card.ID(), BlockID: blockID, NextDues: nextDues, }) @@ -270,10 +295,21 @@ func GetDueFlashcards(deckID string) (ret []*Flashcard, err error) { } if "" == deckID { - return getAllDueFlashcards() + ret = getAllDueFlashcards() + return } + ret = getDueFlashcards(deckID) + return +} + +func getDueFlashcards(deckID string) (ret []*Flashcard) { deck := Decks[deckID] + if nil == deck { + logging.LogWarnf("deck not found [%s]", deckID) + return + } + cards := deck.Dues() now := time.Now() for _, card := range cards { @@ -285,11 +321,12 @@ func GetDueFlashcards(deckID string) (ret []*Flashcard, err error) { nextDues := map[riff.Rating]string{} for rating, due := range card.NextDues() { - nextDues[rating] = strings.TrimSpace(humanize.RelTime(due, now, "", "")) + nextDues[rating] = strings.TrimSpace(util.HumanizeRelTime(due, now, Conf.Lang)) } ret = append(ret, &Flashcard{ DeckID: deckID, + CardID: card.ID(), BlockID: blockID, NextDues: nextDues, }) @@ -300,7 +337,7 @@ func GetDueFlashcards(deckID string) (ret []*Flashcard, err error) { return } -func getAllDueFlashcards() (ret []*Flashcard, err error) { +func getAllDueFlashcards() (ret []*Flashcard) { blockIDs := map[string]bool{} now := time.Now() for _, deck := range Decks { @@ -317,11 +354,12 @@ func getAllDueFlashcards() (ret []*Flashcard, err error) { nextDues := map[riff.Rating]string{} for rating, due := range card.NextDues() { - nextDues[rating] = strings.TrimSpace(humanize.RelTime(due, now, "", "")) + nextDues[rating] = strings.TrimSpace(util.HumanizeRelTime(due, now, Conf.Lang)) } ret = append(ret, &Flashcard{ DeckID: deck.ID, + CardID: card.ID(), BlockID: blockID, NextDues: nextDues, }) diff --git a/kernel/model/mount.go b/kernel/model/mount.go index 06b419bfb..40eb6a6e3 100644 --- a/kernel/model/mount.go +++ b/kernel/model/mount.go @@ -162,8 +162,8 @@ func Mount(boxID string) (alreadyMount bool, err error) { box.SaveConf(boxConf) } - if Conf.Newbie { - Conf.Newbie = false + if Conf.OpenHelp { + Conf.OpenHelp = false Conf.Save() } diff --git a/kernel/util/time.go b/kernel/util/time.go index 433dd2c55..457eba976 100644 --- a/kernel/util/time.go +++ b/kernel/util/time.go @@ -17,7 +17,11 @@ package util import ( + "math" + "strings" "time" + + "github.com/dustin/go-humanize" ) func Millisecond2Time(t int64) time.Time { @@ -33,3 +37,37 @@ func CurrentTimeMillis() int64 { func CurrentTimeSecondsStr() string { return time.Now().Format("20060102150405") } + +func HumanizeRelTime(a time.Time, b time.Time, lang string) string { + _, magnitudes := humanizeTimeMagnitudes(lang) + return strings.TrimSpace(humanize.CustomRelTime(a, b, "", "", magnitudes)) +} + +func HumanizeTime(then time.Time, lang string) string { + labels, magnitudes := humanizeTimeMagnitudes(lang) + return strings.TrimSpace(humanize.CustomRelTime(then, time.Now(), labels["albl"].(string), labels["blbl"].(string), magnitudes)) +} + +func humanizeTimeMagnitudes(lang string) (labels map[string]interface{}, magnitudes []humanize.RelTimeMagnitude) { + labels = TimeLangs[lang] + magnitudes = []humanize.RelTimeMagnitude{ + {time.Second, labels["now"].(string), time.Second}, + {2 * time.Second, labels["1s"].(string), 1}, + {time.Minute, labels["xs"].(string), time.Second}, + {2 * time.Minute, labels["1m"].(string), 1}, + {time.Hour, labels["xm"].(string), time.Minute}, + {2 * time.Hour, labels["1h"].(string), 1}, + {humanize.Day, labels["xh"].(string), time.Hour}, + {2 * humanize.Day, labels["1d"].(string), 1}, + {humanize.Week, labels["xd"].(string), humanize.Day}, + {2 * humanize.Week, labels["1w"].(string), 1}, + {humanize.Month, labels["xw"].(string), humanize.Week}, + {2 * humanize.Month, labels["1M"].(string), 1}, + {humanize.Year, labels["xM"].(string), humanize.Month}, + {18 * humanize.Month, labels["1y"].(string), 1}, + {2 * humanize.Year, labels["2y"].(string), 1}, + {humanize.LongTime, labels["xy"].(string), humanize.Year}, + {math.MaxInt64, labels["max"].(string), 1}, + } + return +} diff --git a/kernel/util/working.go b/kernel/util/working.go index 5a45fa5b4..00a1c3112 100644 --- a/kernel/util/working.go +++ b/kernel/util/working.go @@ -180,15 +180,12 @@ var ( SnippetsPath string // 数据目录下的 snippets/ 路径 UIProcessIDs = sync.Map{} // UI 进程 ID - - IsNewbie bool // 是否是第一次安装 ) func initWorkspaceDir(workspaceArg string) { userHomeConfDir := filepath.Join(HomeDir, ".config", "siyuan") workspaceConf := filepath.Join(userHomeConfDir, "workspace.json") if !gulu.File.IsExist(workspaceConf) { - IsNewbie = ContainerStd == Container // 只有桌面端需要设置新手标识,前端自动挂载帮助文档 if err := os.MkdirAll(userHomeConfDir, 0755); nil != err && !os.IsExist(err) { log.Printf("create user home conf folder [%s] failed: %s", userHomeConfDir, err) os.Exit(ExitCodeCreateConfDirErr)