diff --git a/backend/buserr/errors.go b/backend/buserr/errors.go index 15fed1a8e..82d42e8b4 100644 --- a/backend/buserr/errors.go +++ b/backend/buserr/errors.go @@ -76,3 +76,14 @@ func WithNameAndErr(Key string, name string, err error) BusinessError { Err: err, } } + +func WithName(Key string, name string) BusinessError { + paramMap := map[string]interface{}{} + if name != "" { + paramMap["name"] = name + } + return BusinessError{ + Msg: Key, + Map: paramMap, + } +} diff --git a/backend/constant/errs.go b/backend/constant/errs.go index 818f34a45..0d46ff1ec 100644 --- a/backend/constant/errs.go +++ b/backend/constant/errs.go @@ -89,6 +89,7 @@ var ( ErrFileIsExit = "ErrFileIsExit" ErrFileUpload = "ErrFileUpload" ErrFileDownloadDir = "ErrFileDownloadDir" + ErrCmdNotFound = "ErrCmdNotFound" ) // mysql diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index 029975404..4a7e0812e 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -62,6 +62,7 @@ ErrLinkPathNotFound: "Target path does not exist!" ErrFileIsExit: "File already exists!" ErrFileUpload: "Failed to upload file {{.name}} {{.detail}}" ErrFileDownloadDir: "Download folder not supported" +ErrCmdNotFound: "{{ .name}} command does not exist, please install this command on the host first" #website ErrDomainIsExist: "Domain is already exist" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index f5822bb96..ceef285a3 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -62,6 +62,7 @@ ErrLinkPathNotFound: "目標路徑不存在!" ErrFileIsExit: "文件已存在!" ErrFileUpload: "{{ .name }} 上傳文件失敗 {{ .detail}}" ErrFileDownloadDir: "不支持下載文件夾" +ErrCmdNotFound: "{{ .name}} 命令不存在,請先在宿主機安裝此命令" #website ErrDomainIsExist: "域名已存在" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index 9039e1dca..5dd79d853 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -62,6 +62,7 @@ ErrLinkPathNotFound: "目标路径不存在!" ErrFileIsExit: "文件已存在!" ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail}}" ErrFileDownloadDir: "不支持下载文件夹" +ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令" #website ErrDomainIsExist: "域名已存在" diff --git a/backend/utils/files/archiver.go b/backend/utils/files/archiver.go new file mode 100644 index 000000000..57b7e8c83 --- /dev/null +++ b/backend/utils/files/archiver.go @@ -0,0 +1,31 @@ +package files + +import ( + "github.com/1Panel-dev/1Panel/backend/buserr" + "github.com/1Panel-dev/1Panel/backend/constant" + "github.com/1Panel-dev/1Panel/backend/utils/cmd" +) + +type ShellArchiver interface { + Compress() error + Extract(dstDir string) error +} + +func NewShellArchiver(compressType CompressType) (*TarArchiver, error) { + switch compressType { + case Tar: + if err := checkCmdAvailability("tar"); err != nil { + return nil, err + } + return NewTarArchiver(compressType), nil + default: + return nil, buserr.New("unsupported compress type") + } +} + +func checkCmdAvailability(cmdStr string) error { + if cmd.Which(cmdStr) { + return nil + } + return buserr.WithName(cmdStr, constant.ErrCmdNotFound) +} diff --git a/backend/utils/files/file_op.go b/backend/utils/files/file_op.go index f8c0ef98a..2194f1c21 100644 --- a/backend/utils/files/file_op.go +++ b/backend/utils/files/file_op.go @@ -339,19 +339,7 @@ func (f FileOp) GetDirSize(path string) (float64, error) { return dirSize, nil } -type CompressType string - -const ( - Zip CompressType = "zip" - Gz CompressType = "gz" - Bz2 CompressType = "bz2" - Tar CompressType = "tar" - TarGz CompressType = "tar.gz" - Xz CompressType = "xz" -) - func getFormat(cType CompressType) archiver.CompressedArchive { - format := archiver.CompressedArchive{} switch cType { case Tar: @@ -426,58 +414,68 @@ func decodeGBK(input string) (string, error) { } func (f FileOp) Decompress(srcFile string, dst string, cType CompressType) error { - format := getFormat(cType) - - handler := func(ctx context.Context, archFile archiver.File) error { - info := archFile.FileInfo - if isIgnoreFile(archFile.Name()) { - return nil + switch cType { + case Tar: + shellArchiver, err := NewShellArchiver(cType) + if err != nil { + return err } - fileName := archFile.NameInArchive - var err error - if header, ok := archFile.Header.(cZip.FileHeader); ok { - if header.NonUTF8 && header.Flags == 0 { - fileName, err = decodeGBK(fileName) - if err != nil { - return err + shellArchiver.FilePath = srcFile + return shellArchiver.Extract(dst) + default: + format := getFormat(cType) + handler := func(ctx context.Context, archFile archiver.File) error { + info := archFile.FileInfo + if isIgnoreFile(archFile.Name()) { + return nil + } + fileName := archFile.NameInArchive + var err error + if header, ok := archFile.Header.(cZip.FileHeader); ok { + if header.NonUTF8 && header.Flags == 0 { + fileName, err = decodeGBK(fileName) + if err != nil { + return err + } } } - } - filePath := filepath.Join(dst, fileName) - if archFile.FileInfo.IsDir() { - if err := f.Fs.MkdirAll(filePath, info.Mode()); err != nil { + filePath := filepath.Join(dst, fileName) + if archFile.FileInfo.IsDir() { + if err := f.Fs.MkdirAll(filePath, info.Mode()); err != nil { + return err + } + return nil + } else { + parentDir := path.Dir(filePath) + if !f.Stat(parentDir) { + if err := f.Fs.MkdirAll(parentDir, info.Mode()); err != nil { + return err + } + } + } + fr, err := archFile.Open() + if err != nil { return err } - return nil - } else { - parentDir := path.Dir(filePath) - if !f.Stat(parentDir) { - if err := f.Fs.MkdirAll(parentDir, info.Mode()); err != nil { - return err - } + defer fr.Close() + fw, err := f.Fs.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, info.Mode()) + if err != nil { + return err + } + defer fw.Close() + if _, err := io.Copy(fw, fr); err != nil { + return err } - } - fr, err := archFile.Open() - if err != nil { - return err - } - defer fr.Close() - fw, err := f.Fs.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, info.Mode()) - if err != nil { - return err - } - defer fw.Close() - if _, err := io.Copy(fw, fr); err != nil { - return err - } - return nil + return nil + } + input, err := f.Fs.Open(srcFile) + if err != nil { + return err + } + return format.Extract(context.Background(), input, nil, handler) } - input, err := f.Fs.Open(srcFile) - if err != nil { - return err - } - return format.Extract(context.Background(), input, nil, handler) + return nil } func (f FileOp) Backup(srcFile string) (string, error) { diff --git a/backend/utils/files/fileinfo.go b/backend/utils/files/fileinfo.go index 50fd7d30b..11948ad12 100644 --- a/backend/utils/files/fileinfo.go +++ b/backend/utils/files/fileinfo.go @@ -335,3 +335,14 @@ func min(x, y int) int { } return y } + +type CompressType string + +const ( + Zip CompressType = "zip" + Gz CompressType = "gz" + Bz2 CompressType = "bz2" + Tar CompressType = "tar" + TarGz CompressType = "tar.gz" + Xz CompressType = "xz" +) diff --git a/backend/utils/files/tar.go b/backend/utils/files/tar.go new file mode 100644 index 000000000..92bbc5abd --- /dev/null +++ b/backend/utils/files/tar.go @@ -0,0 +1,40 @@ +package files + +import ( + "fmt" + "github.com/1Panel-dev/1Panel/backend/utils/cmd" + "strings" +) + +type TarArchiver struct { + Cmd string + FilePath string + CompressType CompressType +} + +func NewTarArchiver(compressType CompressType) *TarArchiver { + return &TarArchiver{ + Cmd: "tar", + CompressType: compressType, + } +} + +func (t TarArchiver) Compress(SourcePaths []string) error { + return cmd.ExecCmd(fmt.Sprintf("%s %s %s %s", t.Cmd, t.getOptionStr("compress"), t.FilePath, strings.Join(SourcePaths, " "))) +} + +func (t TarArchiver) Extract(dstDir string) error { + return cmd.ExecCmd(fmt.Sprintf("%s %s %s -C %s", t.Cmd, t.getOptionStr("extract"), t.FilePath, dstDir)) +} + +func (t TarArchiver) getOptionStr(Option string) string { + switch t.CompressType { + case Tar: + if Option == "compress" { + return "cvf" + } else { + return "xf" + } + } + return "" +} diff --git a/frontend/src/global/mimetype.ts b/frontend/src/global/mimetype.ts index 4fd2ecf48..8a5d1f1c0 100644 --- a/frontend/src/global/mimetype.ts +++ b/frontend/src/global/mimetype.ts @@ -15,6 +15,7 @@ export const Mimetypes = new Map([ ['application/x-gzip-compressed', CompressType.TarGz], ['gzip/document', CompressType.TarGz], ['application/x-xz', CompressType.Xz], + ['application/octet-stream', CompressType.Tar], ]); export const Languages = [ diff --git a/frontend/src/views/host/file-management/index.vue b/frontend/src/views/host/file-management/index.vue index 9d2369a12..8b9ceab19 100644 --- a/frontend/src/views/host/file-management/index.vue +++ b/frontend/src/views/host/file-management/index.vue @@ -502,6 +502,7 @@ const openCompress = (items: File.File[]) => { }; const openDeCompress = (item: File.File) => { + console.log(item.mimeType); if (Mimetypes.get(item.mimeType) == undefined) { MsgWarning(i18n.global.t('file.canNotDeCompress')); return;