diff --git a/API.md b/API.md index bddad934f..878023529 100644 --- a/API.md +++ b/API.md @@ -47,6 +47,7 @@ * [List files](#List-files) * [Export](#Export) * [Export Markdown](#Export-Markdown) + * [Export Files and Folders](#Export-files-and-folders) * [Conversion](#Conversion) * [Pandoc](#Pandoc) * [Notification](#Notification) @@ -1118,6 +1119,45 @@ View API token in Settings - About, request header: `Authorization: T * `hPath`: human-readable path * `content`: Markdown content +### Export files and folders + +* `/api/export/exportResources` +* Parameters + + ```json + { + "paths": [ + "/conf/appearance/boot", + "/conf/appearance/langs", + "/conf/appearance/emojis/conf.json", + "/conf/appearance/icons/index.html", + ], + "name": "zip-file-name" + } + ``` + + * `paths`: A list of file or folder paths to be exported, the same filename/folder name will be overwritten + * `name`: (Optional) The exported file name, which defaults to `export-YYYY-MM-DD_hh-mm-ss.zip` when not set +* Return value + + ```json + { + "code": 0, + "msg": "", + "data": { + "path": "temp/export/zip-file-name.zip" + } + } + ``` + + * `path`: The path of `*.zip` file created + * The directory structure in `zip-file-name.zip` is as follows: + * `zip-file-name` + * `boot` + * `langs` + * `conf.json` + * `index.html` + ## Conversion ### Pandoc diff --git a/API_zh_CN.md b/API_zh_CN.md index 76ed7ba2f..bb36a0564 100644 --- a/API_zh_CN.md +++ b/API_zh_CN.md @@ -47,6 +47,7 @@ * [列出文件](#列出文件) * [导出](#导出) * [导出 Markdown 文本](#导出-markdown-文本) + * [导出文件与目录](#导出文件与目录) * [转换](#转换) * [Pandoc](#Pandoc) * [通知](#通知) @@ -1110,6 +1111,45 @@ * `hPath`:人类可读的路径 * `content`:Markdown 内容 +### 导出文件与目录 + +* `/api/export/exportResources` +* 参数 + + ```json + { + "paths": [ + "/conf/appearance/boot", + "/conf/appearance/langs", + "/conf/appearance/emojis/conf.json", + "/conf/appearance/icons/index.html", + ], + "name": "zip-file-name" + } + ``` + + * `paths`:要导出的文件或文件夹路径列表,相同名称的文件/文件夹会被覆盖 + * `name`:(可选)导出的文件名,未设置时默认为 `export-YYYY-MM-DD_hh-mm-ss.zip` +* 返回值 + + ```json + { + "code": 0, + "msg": "", + "data": { + "path": "temp/export/zip-file-name.zip" + } + } + ``` + + * `path`:创建的 `*.zip` 文件路径 + * `zip-file-name.zip` 中的目录结构如下所示: + * `zip-file-name` + * `boot` + * `langs` + * `conf.json` + * `index.html` + ## 转换 ### Pandoc diff --git a/kernel/api/export.go b/kernel/api/export.go index bddbeed36..536f0806f 100644 --- a/kernel/api/export.go +++ b/kernel/api/export.go @@ -23,6 +23,7 @@ import ( "path" "path/filepath" "strings" + "time" "github.com/88250/gulu" "github.com/gin-gonic/gin" @@ -240,6 +241,42 @@ func exportData(c *gin.Context) { } } +func exportResources(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, ok := util.JsonArg(c, ret) + if !ok { + return + } + + var name string + if nil != arg["name"] { + name = util.TruncateLenFileName(arg["name"].(string)) + } + if name == "" { + name = time.Now().Format("export-2006-01-02_15-04-05") // 生成的 *.zip 文件主文件名 + } + + var resourcePaths []string // 文件/文件夹在工作空间中的路径 + if nil != arg["paths"] { + for _, resourcePath := range arg["paths"].([]interface{}) { + resourcePaths = append(resourcePaths, resourcePath.(string)) + } + } + + zipFilePath, err := model.ExportResources(resourcePaths, name) + if nil != err { + ret.Code = 1 + ret.Msg = err.Error() + ret.Data = map[string]interface{}{"closeTimeout": 7000} + return + } + ret.Data = map[string]interface{}{ + "path": zipFilePath, // 相对于工作空间目录的路径 + } +} + func batchExportMd(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/api/router.go b/kernel/api/router.go index 79074fc53..ada345855 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -246,6 +246,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/export/exportDocx", model.CheckAuth, exportDocx) ginServer.Handle("POST", "/api/export/processPDF", model.CheckAuth, processPDF) ginServer.Handle("POST", "/api/export/preview", model.CheckAuth, exportPreview) + ginServer.Handle("POST", "/api/export/exportResources", model.CheckAuth, exportResources) ginServer.Handle("POST", "/api/export/exportAsFile", model.CheckAuth, exportAsFile) ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, exportData) ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, exportDataInFolder) diff --git a/kernel/model/export.go b/kernel/model/export.go index 1c2996979..1eca4c1ad 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -324,6 +324,50 @@ func exportData(exportFolder string) (zipPath string, err error) { return } +func ExportResources(resourcePaths []string, mainName string) (exportFilePath string, err error) { + WaitForWritingFiles() + + // 用于导出的临时文件夹完整路径 + exportFolderPath := filepath.Join(util.TempDir, "export", mainName) + if err = os.MkdirAll(exportFolderPath, 0755); nil != err { + logging.LogErrorf("create export temp folder failed: %s", err) + return + } + + // 将需要导出的文件/文件夹复制到临时文件夹 + for _, resourcePath := range resourcePaths { + resourceFullPath := filepath.Join(util.WorkspaceDir, resourcePath) // 资源完整路径 + resourceBaseName := filepath.Base(resourceFullPath) // 资源名称 + resourceCopyPath := filepath.Join(exportFolderPath, resourceBaseName) // 资源副本完整路径 + if err = filelock.Copy(resourceFullPath, resourceCopyPath); nil != err { + logging.LogErrorf("copy resource will be exported from [%s] to [%s] failed: %s", resourcePath, resourceCopyPath, err) + err = fmt.Errorf(Conf.Language(14), err.Error()) + return + } + } + + zipFilePath := exportFolderPath + ".zip" // 导出的 *.zip 文件完整路径 + zip, err := gulu.Zip.Create(zipFilePath) + if nil != err { + logging.LogErrorf("create export zip [%s] failed: %s", zipFilePath, err) + return + } + + if err = zip.AddDirectory(mainName, exportFolderPath); nil != err { + logging.LogErrorf("create export zip [%s] failed: %s", exportFolderPath, err) + return + } + + if err = zip.Close(); nil != err { + logging.LogErrorf("close export zip failed: %s", err) + } + + os.RemoveAll(exportFolderPath) + + exportFilePath = path.Join("temp", "export", mainName+".zip") // 导出的 *.zip 文件相对于工作区目录的路径 + return +} + func Preview(id string) (retStdHTML string, retOutline []*Path) { tree, _ := loadTreeByBlockID(id) tree = exportTree(tree, false, false, false,