diff --git a/pkg/filesystem/archive.go b/pkg/filesystem/archive.go index f1bc418..400f9b6 100644 --- a/pkg/filesystem/archive.go +++ b/pkg/filesystem/archive.go @@ -2,11 +2,9 @@ package filesystem import ( "archive/zip" - "bytes" "context" "fmt" "io" - "io/ioutil" "os" "path" "path/filepath" @@ -18,8 +16,7 @@ import ( "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" "github.com/cloudreve/Cloudreve/v3/pkg/util" "github.com/gin-gonic/gin" - "golang.org/x/text/encoding/simplifiedchinese" - "golang.org/x/text/transform" + "github.com/mholt/archiver/v4" ) /* =============== @@ -206,21 +203,41 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst string) error { } defer zipFile.Close() - _, err = io.Copy(zipFile, fileStream) + // 下载前先判断是否是可解压的格式 + format, readStream, err := archiver.Identify(fs.FileTarget[0].SourceName, fileStream) if err != nil { - util.Log().Warning("无法写入临时压缩文件 %s , %s", tempZipFilePath, err) + util.Log().Warning("无法识别文件格式 %s , %s", fs.FileTarget[0].SourceName, err) return err } - zipFile.Close() - fileStream.Close() + extractor, ok := format.(archiver.Extractor) + if !ok { + return fmt.Errorf("file not an extractor %s", fs.FileTarget[0].SourceName) + } - // 解压缩文件 - r, err := zip.OpenReader(tempZipFilePath) - if err != nil { - return err + // 只有zip格式可以多个文件同时上传 + var isZip bool + switch extractor.(type) { + case archiver.Zip: + extractor = archiver.Zip{TextEncoding: "gb18030"} + isZip = true + } + + // 除了zip必须下载到本地,其余的可以边下载边解压 + reader := readStream + if isZip { + _, err = io.Copy(zipFile, readStream) + if err != nil { + util.Log().Warning("无法写入临时压缩文件 %s , %s", tempZipFilePath, err) + return err + } + + fileStream.Close() + + // 设置文件偏移量 + zipFile.Seek(0, io.SeekStart) + reader = zipFile } - defer r.Close() // 重设存储策略 fs.Policy = &fs.User.Policy @@ -236,64 +253,64 @@ func (fs *FileSystem) Decompress(ctx context.Context, src, dst string) error { worker <- i } - for _, f := range r.File { - fileName := f.Name - // 处理非UTF-8编码 - if f.NonUTF8 { - i := bytes.NewReader([]byte(fileName)) - decoder := transform.NewReader(i, simplifiedchinese.GB18030.NewDecoder()) - content, _ := ioutil.ReadAll(decoder) - fileName = string(content) + // 上传文件函数 + uploadFunc := func(fileStream io.ReadCloser, size int64, savePath, rawPath string) { + defer func() { + if isZip { + worker <- 1 + wg.Done() + } + if err := recover(); err != nil { + util.Log().Warning("上传压缩包内文件时出错") + fmt.Println(err) + } + }() + + err := fs.UploadFromStream(ctx, &fsctx.FileStream{ + File: fileStream, + Size: uint64(size), + Name: path.Base(savePath), + VirtualPath: path.Dir(savePath), + }, true) + fileStream.Close() + if err != nil { + util.Log().Debug("无法上传压缩包内的文件%s , %s , 跳过", rawPath, err) } + } - rawPath := util.FormSlash(fileName) + // 解压缩文件,回调函数如果出错会停止解压的下一步进行,全部return nil + err = extractor.Extract(ctx, reader, nil, func(ctx context.Context, f archiver.File) error { + rawPath := util.FormSlash(f.NameInArchive) savePath := path.Join(dst, rawPath) // 路径是否合法 if !strings.HasPrefix(savePath, util.FillSlash(path.Clean(dst))) { - return fmt.Errorf("%s: illegal file path", f.Name) + util.Log().Warning("%s: illegal file path", f.NameInArchive) + return nil } // 如果是目录 - if f.FileInfo().IsDir() { + if f.FileInfo.IsDir() { fs.CreateDirectory(ctx, savePath) - continue + return nil } // 上传文件 fileStream, err := f.Open() if err != nil { util.Log().Warning("无法打开压缩包内文件%s , %s , 跳过", rawPath, err) - continue + return nil } - select { - case <-worker: + if !isZip { + uploadFunc(fileStream, f.FileInfo.Size(), savePath, rawPath) + } else { + <-worker wg.Add(1) - go func(fileStream io.ReadCloser, size int64) { - defer func() { - worker <- 1 - wg.Done() - if err := recover(); err != nil { - util.Log().Warning("上传压缩包内文件时出错") - fmt.Println(err) - } - }() - - err = fs.UploadFromStream(ctx, &fsctx.FileStream{ - File: fileStream, - Size: uint64(size), - Name: path.Base(savePath), - VirtualPath: path.Dir(savePath), - }, true) - fileStream.Close() - if err != nil { - util.Log().Debug("无法上传压缩包内的文件%s , %s , 跳过", rawPath, err) - } - }(fileStream, f.FileInfo().Size()) + go uploadFunc(fileStream, f.FileInfo.Size(), savePath, rawPath) } - - } + return nil + }) wg.Wait() - return nil + return err } diff --git a/service/explorer/objects.go b/service/explorer/objects.go index dab5850..d5adbc4 100644 --- a/service/explorer/objects.go +++ b/service/explorer/objects.go @@ -127,9 +127,19 @@ func (service *ItemDecompressService) CreateDecompressTask(c *gin.Context) seria return serializer.Err(serializer.CodeParamErr, "文件太大", nil) } - // 必须是zip压缩包 - if !strings.HasSuffix(file.Name, ".zip") { - return serializer.Err(serializer.CodeParamErr, "只能解压 ZIP 格式的压缩文件", nil) + // 支持的压缩格式后缀 + var ( + suffixes = []string{".zip", ".gz", ".xz", ".tar", ".rar"} + matched bool + ) + for _, suffix := range suffixes { + if strings.HasSuffix(file.Name, suffix) { + matched = true + break + } + } + if !matched { + return serializer.Err(serializer.CodeParamErr, "不支持该格式的压缩文件", nil) } // 创建任务