diff --git a/kernel/api/router.go b/kernel/api/router.go index e62bbf49a..44f000342 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -63,6 +63,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/system/getChangelog", model.CheckAuth, getChangelog) ginServer.Handle("POST", "/api/system/getNetwork", model.CheckAuth, model.CheckAdminRole, getNetwork) ginServer.Handle("POST", "/api/system/exportConf", model.CheckAuth, model.CheckAdminRole, exportConf) + ginServer.Handle("POST", "/api/system/importConf", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importConf) ginServer.Handle("POST", "/api/storage/setLocalStorage", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setLocalStorage) ginServer.Handle("POST", "/api/storage/getLocalStorage", model.CheckAuth, getLocalStorage) diff --git a/kernel/api/system.go b/kernel/api/system.go index f5f2289cd..8eb975c48 100644 --- a/kernel/api/system.go +++ b/kernel/api/system.go @@ -17,7 +17,7 @@ package api import ( - "github.com/jinzhu/copier" + "io" "net/http" "os" "path/filepath" @@ -25,10 +25,10 @@ import ( "sync" "time" - "github.com/88250/lute" - "github.com/88250/gulu" + "github.com/88250/lute" "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/conf" "github.com/siyuan-note/siyuan/kernel/model" @@ -211,17 +211,19 @@ func exportConf(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) + logging.LogInfof("exporting conf...") + name := "siyuan-conf-" + time.Now().Format("20060102150405") + ".json" tmpDir := filepath.Join(util.TempDir, "export") if err := os.MkdirAll(tmpDir, 0755); err != nil { - logging.LogErrorf("export WebDAV provider failed: %s", err) + logging.LogErrorf("export conf failed: %s", err) ret.Code = -1 ret.Msg = err.Error() return } clonedConf := &model.AppConf{} - if err := copier.Copy(clonedConf, model.Conf); err != nil { + if err := copier.CopyWithOption(clonedConf, model.Conf, copier.Option{IgnoreEmpty: false, DeepCopy: true}); err != nil { logging.LogErrorf("export conf failed: %s", err) ret.Code = -1 ret.Msg = err.Error() @@ -291,6 +293,8 @@ func exportConf(c *gin.Context) { return } + logging.LogInfof("exported conf") + zipPath := "/export/" + name + ".zip" ret.Data = map[string]interface{}{ "name": name, @@ -298,6 +302,96 @@ func exportConf(c *gin.Context) { } } +func importConf(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(200, ret) + + logging.LogInfof("importing conf...") + + form, err := c.MultipartForm() + if err != nil { + logging.LogErrorf("read upload file failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + files := form.File["file"] + if 1 != len(files) { + ret.Code = -1 + ret.Msg = "invalid upload file" + return + } + + f := files[0] + fh, err := f.Open() + if err != nil { + logging.LogErrorf("read upload file failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + data, err := io.ReadAll(fh) + fh.Close() + if err != nil { + logging.LogErrorf("read upload file failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + tmpDir := filepath.Join(util.TempDir, "import") + if err = os.MkdirAll(tmpDir, 0755); err != nil { + logging.LogErrorf("import conf failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + tmp := filepath.Join(tmpDir, f.Filename) + if err = os.WriteFile(tmp, data, 0644); err != nil { + logging.LogErrorf("import conf failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil { + logging.LogErrorf("import conf failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + tmp = filepath.Join(tmpDir, f.Filename[:len(f.Filename)-4]) + data, err = os.ReadFile(tmp) + if err != nil { + logging.LogErrorf("import conf failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + importedConf := model.NewAppConf() + if err = gulu.JSON.UnmarshalJSON(data, importedConf); err != nil { + logging.LogErrorf("import conf failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + if err = copier.CopyWithOption(model.Conf, importedConf, copier.Option{IgnoreEmpty: true, DeepCopy: true}); err != nil { + logging.LogErrorf("import conf failed: %s", err) + ret.Code = -1 + ret.Msg = err.Error() + return + } + + logging.LogInfof("imported conf") + model.Close(false, true, 1) +} + func getConf(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/model/conf.go b/kernel/model/conf.go index 49baf8e79..3b15262b2 100644 --- a/kernel/model/conf.go +++ b/kernel/model/conf.go @@ -51,7 +51,7 @@ var Conf *AppConf // AppConf 维护应用元数据,保存在 ~/.siyuan/conf.json。 type AppConf struct { - LogLevel string `json:"logLevel"` // 日志级别:Off, Trace, Debug, Info, Warn, Error, Fatal + 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 @@ -87,6 +87,10 @@ type AppConf struct { m *sync.Mutex } +func NewAppConf() *AppConf { + return &AppConf{LogLevel: "debug", m: &sync.Mutex{}} +} + func (conf *AppConf) GetUILayout() *conf.UILayout { conf.m.Lock() defer conf.m.Unlock() @@ -114,7 +118,7 @@ func (conf *AppConf) SetUser(user *conf.User) { func InitConf() { initLang() - Conf = &AppConf{LogLevel: "debug", m: &sync.Mutex{}} + Conf = NewAppConf() confPath := filepath.Join(util.ConfDir, "conf.json") if gulu.File.IsExist(confPath) { if data, err := os.ReadFile(confPath); err != nil {