diff --git a/pkg/filesystem/archive.go b/pkg/filesystem/archive.go index 2a8f994..abdb9ce 100644 --- a/pkg/filesystem/archive.go +++ b/pkg/filesystem/archive.go @@ -1,16 +1,193 @@ package filesystem import ( + "archive/zip" "context" + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/util" "io" + "net/http" + "os" ) -/* ============== +/* =============== 压缩/解压缩 - ============== + =============== */ // Compress 创建给定目录和文件的压缩文件 -func (fs *FileSystem) Compress(ctx context.Context, dirs, files []uint) (io.ReadSeeker, error) { - return nil, nil +func (fs *FileSystem) Compress(ctx context.Context, folderIDs, fileIDs []uint, writer http.ResponseWriter) error { + // 查找待压缩目录 + folders, err := model.GetFoldersByIDs(folderIDs, fs.User.ID) + if err != nil && len(folders) != 0 { + return ErrDBListObjects + } + // 查找待压缩文件 + files, err := model.GetFilesByIDs(fileIDs, fs.User.ID) + if err != nil && len(files) != 0 { + return ErrDBListObjects + } + + // 将顶级待处理对象的路径设为根路径 + for i := 0; i < len(folders); i++ { + folders[i].Position = "" + } + for i := 0; i < len(files); i++ { + files[i].Position = "" + } + + // 创建压缩文件 + zipFile, err := os.Create("temp/archive.zip") + if err != nil { + util.Log().Warning("%s", err) + return err + } + defer zipFile.Close() + + //writer.Header().Set("Content-Type", "application/zip") + //writer.Header().Set("Content-Disposition", "attachment; filename=\"archive.zip\"") + + zipWriter := zip.NewWriter(writer) + defer zipWriter.Close() + + ctx, _ = context.WithCancel(context.Background()) + // 压缩各个目录及文件 + for i := 0; i < len(folders); i++ { + fs.doCompress(ctx, nil, &folders[i], zipWriter) + } + for i := 0; i < len(files); i++ { + fs.doCompress(ctx, &files[i], nil, zipWriter) + } + + return nil +} + +func (fs *FileSystem) doCompress(ctx context.Context, file *model.File, folder *model.Folder, zipWriter *zip.Writer) { + // 如果对象是文件 + if file != nil { + // 切换上传策略 + fs.Policy = file.GetPolicy() + err := fs.dispatchHandler() + if err != nil { + util.Log().Warning("无法压缩文件%s,%s", file.Name, err) + return + } + + fileToZip, err := fs.Handler.Get(ctx, file.SourceName) + if err != nil { + util.Log().Debug("Open%s,%s", file.Name, err) + return + } + if closer, ok := fileToZip.(io.Closer); ok { + defer closer.Close() + } + + header := &zip.FileHeader{ + Name: file.Name, + } + + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return + } + + _, err = io.Copy(writer, fileToZip) + util.Log().Debug("开始压缩%s", file.Name) + + //f, err := os.Open(file.SourceName) + //if err != nil { + // util.Log().Debug("Open%s,%s", file.Name, err) + // return + //} + // + //info, err := f.Stat() + //if err != nil { + // util.Log().Debug("Stat%s,%s", file.Name, err) + // return + //} + // + //header, err := zip.FileInfoHeader(info) + //if err != nil { + // util.Log().Debug("header%s,%s", file.Name, err) + // return + //} + // + //writer, err := zipWriter.CreateHeader(header) + //if err != nil { + // util.Log().Debug("压缩出错%s,%s", header.Name, err) + // return + //} + // + //_, err = io.Copy(writer, f) + //if err != nil { + // util.Log().Debug("压缩出错%s,%s", header.Name, err) + // return + //} + // + //if true { + // f.Close() + //} + // + //util.Log().Debug("压缩完成%s", header.Name) + + //pool.Submit(compressJob) + } +} + +// ZipFiles compresses one or many files into a single zip archive file. +// Param 1: filename is the output zip file's name. +// Param 2: files is a list of files to add to the zip. +func ZipFiles(filename string, files []string) error { + + newZipFile, err := os.Create(filename) + if err != nil { + return err + } + defer newZipFile.Close() + + zipWriter := zip.NewWriter(newZipFile) + defer zipWriter.Close() + + // Add files to zip + for _, file := range files { + if err = AddFileToZip(zipWriter, file); err != nil { + return err + } + } + return nil +} + +func AddFileToZip(zipWriter *zip.Writer, filename string) error { + + fileToZip, err := os.Open(filename) + if err != nil { + return err + } + defer fileToZip.Close() + + // Get the file information + info, err := fileToZip.Stat() + if err != nil { + return err + } + + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + // Using FileInfoHeader() above only uses the basename of the file. If we want + // to preserve the folder structure we can overwrite this with the full path. + header.Name = filename + + // Change to deflate to gain better compression + // see http://golang.org/pkg/archive/zip/#pkg-constants + header.Method = zip.Deflate + + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return err + } + _, err = io.Copy(writer, fileToZip) + return err } diff --git a/pkg/task/pool.go b/pkg/task/pool.go index d439b03..6b62f01 100644 --- a/pkg/task/pool.go +++ b/pkg/task/pool.go @@ -77,11 +77,11 @@ func (pool *Pool) Schedule() { } // Wait 等待队列中所有任务完成或有Job返回错误中止 -func (pool *Pool) Wait() { +func (pool *Pool) Wait() chan bool { pool.lock.Lock() pool.waiting = true pool.lock.Unlock() - _ = <-pool.finishSignal + return pool.finishSignal } // Submit 提交新任务 @@ -110,7 +110,11 @@ func (pool *Pool) start(job Job) { err := job.Do() if err != nil { pool.closed = true - close(pool.terminateSignal) + select { + case <-pool.terminateSignal: + default: + close(pool.terminateSignal) + } } pool.lock.Lock() diff --git a/routers/controllers/file.go b/routers/controllers/file.go index ecad5d0..246d4b0 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -22,13 +22,16 @@ func ArchiveAndDownload(c *gin.Context) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - var service explorer.ItemService - if err := c.ShouldBindJSON(&service); err == nil { - res := service.ArchiveAndDownload(ctx, c) - c.JSON(200, res) - } else { - c.JSON(200, ErrorResponse(err)) - } + var service = explorer.ItemService{ + Items: []uint{117, 118, 119, 120, 121, 122, 123}, + Dirs: []uint{}, + } + //if err := c.ShouldBindJSON(&service); err == nil { + _ = service.ArchiveAndDownload(ctx, c) + //c.JSON(200, res) + //} else { + // c.JSON(200, ErrorResponse(err)) + //} } // AnonymousGetContent 匿名获取文件资源 diff --git a/routers/router.go b/routers/router.go index 95d99f0..3f20e3e 100644 --- a/routers/router.go +++ b/routers/router.go @@ -102,7 +102,7 @@ func InitRouter() *gin.Engine { // 取得文件外链 file.GET("source/:id", controllers.GetSource) // 测试用:压缩文件和目录并下載 - file.POST("archive", controllers.ArchiveAndDownload) + file.GET("archive", controllers.ArchiveAndDownload) } // 目录 diff --git a/service/explorer/objects.go b/service/explorer/objects.go index d399b69..ef6681d 100644 --- a/service/explorer/objects.go +++ b/service/explorer/objects.go @@ -2,8 +2,8 @@ package explorer import ( "context" - "fmt" "github.com/HFO4/cloudreve/pkg/filesystem" + "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/gin-gonic/gin" ) @@ -40,14 +40,18 @@ func (service *ItemService) ArchiveAndDownload(ctx context.Context, c *gin.Conte return serializer.Err(serializer.CodeGroupNotAllowed, "当前用户组无法进行此操作", nil) } + //// 写HTTP头 + //c.Header("Content-Type", "application/zip") + //c.Header("Content-Disposition", "attachment; filename=\"archive.zip\"") + // 开始压缩,获取压缩后的stream - rs, err := fs.Compress(ctx, service.Dirs, service.Items) + ctx = context.WithValue(ctx, fsctx.GinCtx, c) + + err = fs.Compress(ctx, service.Dirs, service.Items, c.Writer) if err != nil { - return serializer.Err(serializer.CodeNotSet, "无法创建压缩文件", err) + return serializer.Err(serializer.CodeGroupNotAllowed, "无法创建", nil) } - fmt.Println(rs) - return serializer.Response{ Code: 0, }