Quellcode durchsuchen

:art: Add API `/api/export/exportResources` to export files and folders (#8841)

Yingyi / 颖逸 vor 1 Jahr
Ursprung
Commit
0c7e16e558
5 geänderte Dateien mit 162 neuen und 0 gelöschten Zeilen
  1. 40 0
      API.md
  2. 40 0
      API_zh_CN.md
  3. 37 0
      kernel/api/export.go
  4. 1 0
      kernel/api/router.go
  5. 44 0
      kernel/model/export.go

+ 40 - 0
API.md

@@ -47,6 +47,7 @@
     * [List files](#List-files)
     * [List files](#List-files)
 * [Export](#Export)
 * [Export](#Export)
     * [Export Markdown](#Export-Markdown)
     * [Export Markdown](#Export-Markdown)
+    * [Export Files and Folders](#Export-files-and-folders)
 * [Conversion](#Conversion)
 * [Conversion](#Conversion)
     * [Pandoc](#Pandoc)
     * [Pandoc](#Pandoc)
 * [Notification](#Notification)
 * [Notification](#Notification)
@@ -1118,6 +1119,45 @@ View API token in <kbd>Settings - About</kbd>, request header: `Authorization: T
     * `hPath`: human-readable path
     * `hPath`: human-readable path
     * `content`: Markdown content
     * `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
 ## Conversion
 
 
 ### Pandoc
 ### Pandoc

+ 40 - 0
API_zh_CN.md

@@ -47,6 +47,7 @@
     * [列出文件](#列出文件)
     * [列出文件](#列出文件)
 * [导出](#导出)
 * [导出](#导出)
     * [导出 Markdown 文本](#导出-markdown-文本)
     * [导出 Markdown 文本](#导出-markdown-文本)
+    * [导出文件与目录](#导出文件与目录)
 * [转换](#转换)
 * [转换](#转换)
     * [Pandoc](#Pandoc)
     * [Pandoc](#Pandoc)
 * [通知](#通知)
 * [通知](#通知)
@@ -1110,6 +1111,45 @@
     * `hPath`:人类可读的路径
     * `hPath`:人类可读的路径
     * `content`:Markdown 内容
     * `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
 ### Pandoc

+ 37 - 0
kernel/api/export.go

@@ -23,6 +23,7 @@ import (
 	"path"
 	"path"
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
+	"time"
 
 
 	"github.com/88250/gulu"
 	"github.com/88250/gulu"
 	"github.com/gin-gonic/gin"
 	"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) {
 func batchExportMd(c *gin.Context) {
 	ret := gulu.Ret.NewResult()
 	ret := gulu.Ret.NewResult()
 	defer c.JSON(http.StatusOK, ret)
 	defer c.JSON(http.StatusOK, ret)

+ 1 - 0
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/exportDocx", model.CheckAuth, exportDocx)
 	ginServer.Handle("POST", "/api/export/processPDF", model.CheckAuth, processPDF)
 	ginServer.Handle("POST", "/api/export/processPDF", model.CheckAuth, processPDF)
 	ginServer.Handle("POST", "/api/export/preview", model.CheckAuth, exportPreview)
 	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/exportAsFile", model.CheckAuth, exportAsFile)
 	ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, exportData)
 	ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, exportData)
 	ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, exportDataInFolder)
 	ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, exportDataInFolder)

+ 44 - 0
kernel/model/export.go

@@ -324,6 +324,50 @@ func exportData(exportFolder string) (zipPath string, err error) {
 	return
 	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) {
 func Preview(id string) (retStdHTML string, retOutline []*Path) {
 	tree, _ := loadTreeByBlockID(id)
 	tree, _ := loadTreeByBlockID(id)
 	tree = exportTree(tree, false, false, false,
 	tree = exportTree(tree, false, false, false,