Feat: decompression task

pull/247/head
HFO4 4 years ago
parent e722c33cd5
commit 91e202c7e6

@ -25,11 +25,13 @@ type Group struct {
// GroupOption 用户组其他配置
type GroupOption struct {
ArchiveDownload bool `json:"archive_download,omitempty"`
ArchiveTask bool `json:"archive_task,omitempty"`
OneTimeDownload bool `json:"one_time_download,omitempty"`
ShareDownload bool `json:"share_download,omitempty"`
ShareFree bool `json:"share_free,omitempty"`
ArchiveDownload bool `json:"archive_download,omitempty"`
ArchiveTask bool `json:"archive_task,omitempty"`
CompressSize uint64 `json:"compress_size,omitempty"`
DecompressSize uint64 `json:"decompress_size,omitempty"`
OneTimeDownload bool `json:"one_time_download,omitempty"`
ShareDownload bool `json:"share_download,omitempty"`
ShareFree bool `json:"share_free,omitempty"`
}
// GetAria2Option 获取用户离线下载设备

@ -160,6 +160,7 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
{Name: "aria2_rpcurl", Value: `http://127.0.0.1:6800/`, Type: "aria2"},
{Name: "aria2_options", Value: `{"max-tries":5}`, Type: "aria2"},
{Name: "max_worker_num", Value: `10`, Type: "task"},
{Name: "max_parallel_transfer", Value: `4`, Type: "task"},
{Name: "secret_key", Value: util.RandStringRunes(256), Type: "auth"},
{Name: "temp_path", Value: "temp", Type: "path"},
{Name: "score_enabled", Value: "1", Type: "score"},

@ -10,7 +10,10 @@ import (
"github.com/gin-gonic/gin"
"io"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
)
@ -148,7 +151,7 @@ func (fs *FileSystem) doCompress(ctx context.Context, file *model.File, folder *
// 创建压缩文件头
header := &zip.FileHeader{
Name: filepath.Join(file.Position, file.Name),
Name: filepath.FromSlash(path.Join(file.Position, file.Name)),
Modified: file.UpdatedAt,
UncompressedSize64: file.Size,
}
@ -185,3 +188,117 @@ func (fs *FileSystem) doCompress(ctx context.Context, file *model.File, folder *
}
}
}
// Decompress 解压缩给定压缩文件到dst目录
func (fs *FileSystem) Decompress(ctx context.Context, src, dst string) error {
err := fs.resetFileIfNotExist(ctx, src)
if err != nil {
return err
}
tempZipFilePath := ""
defer func() {
// 结束时删除临时压缩文件
if tempZipFilePath != "" {
if err := os.Remove(tempZipFilePath); err != nil {
util.Log().Warning("无法删除临时压缩文件 %s , %s", tempZipFilePath, err)
}
}
}()
// 下载压缩文件到临时目录
fileStream, err := fs.Handler.Get(ctx, fs.FileTarget[0].SourceName)
if err != nil {
return err
}
tempZipFilePath = filepath.Join(
model.GetSettingByName("temp_path"),
"decompress",
fmt.Sprintf("archive_%d.zip", time.Now().UnixNano()),
)
zipFile, err := util.CreatNestedFile(tempZipFilePath)
if err != nil {
util.Log().Warning("无法创建临时压缩文件 %s , %s", tempZipFilePath, err)
tempZipFilePath = ""
return err
}
defer zipFile.Close()
_, err = io.Copy(zipFile, fileStream)
if err != nil {
util.Log().Warning("无法写入临时压缩文件 %s , %s", tempZipFilePath, err)
return err
}
zipFile.Close()
// 解压缩文件
r, err := zip.OpenReader(tempZipFilePath)
if err != nil {
return err
}
defer r.Close()
// 重设存储策略
fs.Policy = &fs.User.Policy
err = fs.DispatchHandler()
if err != nil {
return err
}
var wg sync.WaitGroup
parallel := model.GetIntSetting("max_parallel_transfer", 4)
worker := make(chan int, parallel)
for i := 0; i < parallel; i++ {
worker <- i
}
for _, f := range r.File {
rawPath := util.FormSlash(f.Name)
savePath := path.Join(dst, rawPath)
// 路径是否合法
if !strings.HasPrefix(savePath, path.Clean(dst)+"/") {
return fmt.Errorf("%s: illegal file path", f.Name)
}
// 如果是目录
if f.FileInfo().IsDir() {
fs.CreateDirectory(ctx, savePath)
continue
}
// 上传文件
fileStream, err := f.Open()
if err != nil {
util.Log().Warning("无法打开压缩包内文件%s , %s , 跳过", rawPath, err)
continue
}
select {
case <-worker:
go func(fileStream io.ReadCloser, size int64) {
wg.Add(1)
defer func() {
worker <- 1
wg.Done()
if err := recover(); err != nil {
util.Log().Warning("上传压缩包内文件时出错")
fmt.Println(err)
}
}()
err = fs.UploadFromStream(ctx, fileStream, savePath, uint64(size))
fileStream.Close()
if err != nil {
util.Log().Debug("无法上传压缩包内的文件%s , %s , 跳过", rawPath, err)
}
}(fileStream, f.FileInfo().Size())
}
}
wg.Wait()
return nil
}

@ -180,7 +180,7 @@ func (client *Client) UploadChunk(ctx context.Context, uploadURL string, chunk *
func (client *Client) Upload(ctx context.Context, dst string, size int, file io.Reader) error {
// 小文件,使用简单上传接口上传
if size <= int(SmallFileSize) {
_, err := client.SimpleUpload(ctx, dst, file)
_, err := client.SimpleUpload(ctx, dst, file, int64(size))
return err
}
@ -248,11 +248,11 @@ func (client *Client) DeleteUploadSession(ctx context.Context, uploadURL string)
}
// SimpleUpload 上传小文件到dst
func (client *Client) SimpleUpload(ctx context.Context, dst string, body io.Reader) (*UploadResult, error) {
func (client *Client) SimpleUpload(ctx context.Context, dst string, body io.Reader, size int64) (*UploadResult, error) {
dst = strings.TrimPrefix(dst, "/")
requestURL := client.getRequestURL("me/drive/root:/" + dst + ":/content")
res, err := client.request(ctx, "PUT", requestURL, body)
res, err := client.request(ctx, "PUT", requestURL, body, request.WithContentLength(int64(size)))
if err != nil {
return nil, err
}
@ -381,7 +381,7 @@ func (client *Client) MonitorUpload(uploadURL, callbackKey, path string, size ui
case <-time.After(time.Duration(ttl) * time.Second):
// 上传会话到期,仍未完成上传,创建占位符
client.DeleteUploadSession(context.Background(), uploadURL)
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""))
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""), 0)
if err != nil {
util.Log().Debug("无法创建占位文件,%s", err)
}
@ -428,7 +428,7 @@ func (client *Client) MonitorUpload(uploadURL, callbackKey, path string, size ui
// 取消上传会话实测OneDrive取消上传会话后客户端还是可以上传
// 所以上传一个空文件占位,阻止客户端上传
client.DeleteUploadSession(context.Background(), uploadURL)
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""))
_, err := client.SimpleUpload(context.Background(), path, strings.NewReader(""), 0)
if err != nil {
util.Log().Debug("无法创建占位文件,%s", err)
}

@ -12,6 +12,7 @@ var (
ErrInsufficientCapacity = errors.New("容量空间不足")
ErrIllegalObjectName = errors.New("目标名称非法")
ErrClientCanceled = errors.New("客户端取消操作")
ErrRootProtected = errors.New("无法对根目录进行操作")
ErrInsertFileRecord = serializer.NewError(serializer.CodeDBError, "无法插入文件记录", nil)
ErrFileExisted = serializer.NewError(serializer.CodeObjectExist, "同名文件或目录已存在", nil)
ErrFolderExisted = serializer.NewError(serializer.CodeObjectExist, "同名目录已存在", nil)

@ -78,6 +78,8 @@ type FileSystem struct {
DirTarget []model.Folder
// 相对根目录
Root *model.Folder
// 互斥锁
Lock sync.Mutex
/*
@ -110,6 +112,7 @@ func (fs *FileSystem) reset() {
fs.Hooks = nil
fs.Handler = nil
fs.Root = nil
fs.Lock = sync.Mutex{}
}
// NewFileSystem 初始化一个文件系统

@ -31,4 +31,6 @@ const (
ShareKeyCtx
// LimitParentCtx 限制父目录
LimitParentCtx
// IgnoreConflictCtx 忽略重名冲突
IgnoreConflictCtx
)

@ -28,6 +28,15 @@ func (fs *FileSystem) Use(name string, hook Hook) {
fs.Hooks[name] = []Hook{hook}
}
// CleanHooks 清空钩子,name为空表示全部清空
func (fs *FileSystem) CleanHooks(name string) {
if name == "" {
fs.Hooks = nil
} else {
delete(fs.Hooks, name)
}
}
// Trigger 触发钩子,遇到第一个错误时
// 返回错误,后续钩子不会继续执行
func (fs *FileSystem) Trigger(ctx context.Context, name string) error {
@ -247,10 +256,14 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
// 文件存放的虚拟路径
virtualPath := ctx.Value(fsctx.FileHeaderCtx).(FileHeader).GetVirtualPath()
// 检查路径是否存在
// 检查路径是否存在,不存在就创建
isExist, folder := fs.IsPathExist(virtualPath)
if !isExist {
return ErrPathNotExist
newFolder, err := fs.CreateDirectory(ctx, virtualPath)
if err != nil {
return err
}
folder = newFolder
}
// 检查文件是否存在

@ -328,8 +328,12 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu
return objects, nil
}
// CreateDirectory 根据给定的完整创建目录,不会递归创建
func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) error {
// CreateDirectory 根据给定的完整创建目录,支持递归创建
func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) (*model.Folder, error) {
if fullPath == "/" {
return nil, ErrRootProtected
}
// 获取要创建目录的父路径和目录名
fullPath = path.Clean(fullPath)
base := path.Dir(fullPath)
@ -340,18 +344,26 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) erro
// 检查目录名是否合法
if !fs.ValidateLegalName(ctx, dir) {
return ErrIllegalObjectName
return nil, ErrIllegalObjectName
}
// 父目录是否存在
isExist, parent := fs.IsPathExist(base)
if !isExist {
return ErrPathNotExist
// 递归创建父目录
if _, ok := ctx.Value(fsctx.IgnoreConflictCtx).(bool); !ok {
ctx = context.WithValue(ctx, fsctx.IgnoreConflictCtx, true)
}
newParent, err := fs.CreateDirectory(ctx, base)
if err != nil {
return nil, err
}
parent = newParent
}
// 是否有同名文件
if ok, _ := fs.IsChildFileExist(parent, dir); ok {
return ErrFileExisted
return nil, ErrFileExisted
}
// 创建目录
@ -363,9 +375,12 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) erro
_, err := newFolder.Create()
if err != nil {
return ErrFolderExisted
if _, ok := ctx.Value(fsctx.IgnoreConflictCtx).(bool); !ok {
return nil, ErrFolderExisted
}
}
return nil
return &newFolder, nil
}
// SaveTo 将别人分享的文件转存到目标路径下

@ -146,12 +146,12 @@ func TestFileSystem_CreateDirectory(t *testing.T) {
ctx := context.Background()
// 目录名非法
err := fs.CreateDirectory(ctx, "/ad/a+?")
_, err := fs.CreateDirectory(ctx, "/ad/a+?")
asserts.Equal(ErrIllegalObjectName, err)
// 父目录不存在
mock.ExpectQuery("SELECT(.+)folders").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}))
err = fs.CreateDirectory(ctx, "/ad/ab")
_, err = fs.CreateDirectory(ctx, "/ad/ab")
asserts.Equal(ErrPathNotExist, err)
asserts.NoError(mock.ExpectationsWereMet())
@ -166,7 +166,7 @@ func TestFileSystem_CreateDirectory(t *testing.T) {
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(2, 1))
mock.ExpectQuery("SELECT(.+)files").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "ab"))
err = fs.CreateDirectory(ctx, "/ad/ab")
_, err = fs.CreateDirectory(ctx, "/ad/ab")
asserts.Equal(ErrFileExisted, err)
asserts.NoError(mock.ExpectationsWereMet())
@ -183,7 +183,7 @@ func TestFileSystem_CreateDirectory(t *testing.T) {
mock.ExpectBegin()
mock.ExpectExec("INSERT(.+)").WillReturnError(errors.New("s"))
mock.ExpectRollback()
err = fs.CreateDirectory(ctx, "/ad/ab")
_, err = fs.CreateDirectory(ctx, "/ad/ab")
asserts.Error(err)
asserts.NoError(mock.ExpectationsWereMet())
@ -201,7 +201,7 @@ func TestFileSystem_CreateDirectory(t *testing.T) {
mock.ExpectBegin()
mock.ExpectExec("INSERT(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
err = fs.CreateDirectory(ctx, "/ad/ab")
_, err = fs.CreateDirectory(ctx, "/ad/ab")
asserts.NoError(err)
asserts.NoError(mock.ExpectationsWereMet())
}

@ -9,6 +9,7 @@ import (
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"io"
"os"
"path"
)
@ -186,8 +187,45 @@ func (fs *FileSystem) GetUploadToken(ctx context.Context, path string, size uint
return &credential, nil
}
// UploadFromStream 从文件流上传文件
func (fs *FileSystem) UploadFromStream(ctx context.Context, src io.ReadCloser, dst string, size uint64) error {
// 构建文件头
fileName := path.Base(dst)
filePath := path.Dir(dst)
fileData := local.FileStream{
File: src,
Size: size,
Name: fileName,
VirtualPath: filePath,
}
// 给文件系统分配钩子
fs.Lock.Lock()
if fs.Hooks == nil {
fs.Use("BeforeUpload", HookValidateFile)
fs.Use("BeforeUpload", HookValidateCapacity)
fs.Use("AfterUploadCanceled", HookDeleteTempFile)
fs.Use("AfterUploadCanceled", HookGiveBackCapacity)
fs.Use("AfterUpload", GenericAfterUpload)
fs.Use("AfterValidateFailed", HookDeleteTempFile)
fs.Use("AfterValidateFailed", HookGiveBackCapacity)
fs.Use("AfterUploadFailed", HookGiveBackCapacity)
}
fs.Lock.Unlock()
// 开始上传
return fs.Upload(ctx, fileData)
}
// UploadFromPath 将本机已有文件上传到用户的文件系统
func (fs *FileSystem) UploadFromPath(ctx context.Context, src, dst string) error {
// 重设存储策略
fs.Policy = &fs.User.Policy
err := fs.DispatchHandler()
if err != nil {
return err
}
file, err := os.Open(src)
if err != nil {
return err
@ -201,26 +239,6 @@ func (fs *FileSystem) UploadFromPath(ctx context.Context, src, dst string) error
}
size := fi.Size()
// 构建文件头
fileName := path.Base(dst)
filePath := path.Dir(dst)
fileData := local.FileStream{
File: file,
Size: uint64(size),
Name: fileName,
VirtualPath: filePath,
}
// 给文件系统分配钩子
fs.Use("BeforeUpload", HookValidateFile)
fs.Use("BeforeUpload", HookValidateCapacity)
fs.Use("AfterUploadCanceled", HookDeleteTempFile)
fs.Use("AfterUploadCanceled", HookGiveBackCapacity)
fs.Use("AfterUpload", GenericAfterUpload)
fs.Use("AfterValidateFailed", HookDeleteTempFile)
fs.Use("AfterValidateFailed", HookGiveBackCapacity)
fs.Use("AfterUploadFailed", HookGiveBackCapacity)
// 开始上传
return fs.Upload(ctx, fileData)
return fs.UploadFromStream(ctx, file, dst, uint64(size))
}

@ -0,0 +1,127 @@
package task
import (
"context"
"encoding/json"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem"
)
// DecompressTask 文件压缩任务
type DecompressTask struct {
User *model.User
TaskModel *model.Task
TaskProps DecompressProps
Err *JobError
zipPath string
}
// DecompressProps 压缩任务属性
type DecompressProps struct {
Src string `json:"src"`
Dst string `json:"dst"`
}
// Props 获取任务属性
func (job *DecompressTask) Props() string {
res, _ := json.Marshal(job.TaskProps)
return string(res)
}
// Type 获取任务状态
func (job *DecompressTask) Type() int {
return DecompressTaskType
}
// Creator 获取创建者ID
func (job *DecompressTask) Creator() uint {
return job.User.ID
}
// Model 获取任务的数据库模型
func (job *DecompressTask) Model() *model.Task {
return job.TaskModel
}
// SetStatus 设定状态
func (job *DecompressTask) SetStatus(status int) {
job.TaskModel.SetStatus(status)
}
// SetError 设定任务失败信息
func (job *DecompressTask) SetError(err *JobError) {
job.Err = err
res, _ := json.Marshal(job.Err)
job.TaskModel.SetError(string(res))
}
// SetErrorMsg 设定任务失败信息
func (job *DecompressTask) SetErrorMsg(msg string, err error) {
jobErr := &JobError{Msg: msg}
if err != nil {
jobErr.Error = err.Error()
}
job.SetError(jobErr)
}
// GetError 返回任务失败信息
func (job *DecompressTask) GetError() *JobError {
return job.Err
}
// Do 开始执行任务
func (job *DecompressTask) Do() {
// 创建文件系统
fs, err := filesystem.NewFileSystem(job.User)
if err != nil {
job.SetErrorMsg("无法创建文件系统", err)
return
}
defer fs.Recycle()
err = fs.Decompress(context.Background(), job.TaskProps.Src, job.TaskProps.Dst)
if err != nil {
job.SetErrorMsg("解压缩失败", err)
return
}
}
// NewDecompressTask 新建压缩任务
func NewDecompressTask(user *model.User, src, dst string) (Job, error) {
newTask := &DecompressTask{
User: user,
TaskProps: DecompressProps{
Src: src,
Dst: dst,
},
}
record, err := Record(newTask)
if err != nil {
return nil, err
}
newTask.TaskModel = record
return newTask, nil
}
// NewDecompressTaskFromModel 从数据库记录中恢复压缩任务
func NewDecompressTaskFromModel(task *model.Task) (Job, error) {
user, err := model.GetUserByID(task.UserID)
if err != nil {
return nil, err
}
newTask := &DecompressTask{
User: &user,
TaskModel: task,
}
err = json.Unmarshal([]byte(task.Props), &newTask.TaskProps)
if err != nil {
return nil, err
}
return newTask, nil
}

@ -9,6 +9,8 @@ import (
const (
// CompressTaskType 压缩任务
CompressTaskType = iota
// DecompressTaskType 解压缩任务
DecompressTaskType
)
// 任务状态
@ -27,8 +29,10 @@ const (
// 任务进度
const (
// PendingProgress 等待中
PendingProgress = iota
// Compressing 压缩中
CompressingProgress = iota
CompressingProgress
// Decompressing 解压缩中
DecompressingProgress
// Downloading 下载中
@ -51,7 +55,8 @@ type Job interface {
// JobError 任务失败信息
type JobError struct {
Msg string
Msg string `json:"msg,omitempty"`
Error string `json:"error,omitempty"`
}
// Record 将任务记录到数据库中
@ -92,6 +97,8 @@ func GetJobFromModel(task *model.Task) (Job, error) {
switch task.Type {
case CompressTaskType:
return NewCompressTaskFromModel(task)
case DecompressTaskType:
return NewDecompressTaskFromModel(task)
default:
return nil, ErrUnknownTaskType
}

@ -1,6 +1,9 @@
package util
import "strings"
import (
"path"
"strings"
)
// DotPathToStandardPath 将","分割的路径转换为标准路径
func DotPathToStandardPath(path string) string {
@ -37,3 +40,8 @@ func SplitPath(path string) []string {
pathSplit[0] = "/"
return pathSplit
}
// FormSlash 将path中的反斜杠'\'替换为'/'
func FormSlash(old string) string {
return path.Clean(strings.ReplaceAll(old, "\\", "/"))
}

@ -326,6 +326,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesyst
exist, originFile := fs.IsFileExist(reqPath)
if exist {
// 已存在,为更新操作
fs.Use("BeforeUpload", filesystem.HookValidateFile)
fs.Use("BeforeUpload", filesystem.HookResetPolicy)
fs.Use("BeforeUpload", filesystem.HookChangeCapacity)
@ -382,7 +383,7 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *filesy
if r.ContentLength > 0 {
return http.StatusUnsupportedMediaType, nil
}
if err := fs.CreateDirectory(ctx, reqPath); err != nil {
if _, err := fs.CreateDirectory(ctx, reqPath); err != nil {
return http.StatusConflict, err
}
return http.StatusCreated, nil

@ -58,6 +58,17 @@ func Compress(c *gin.Context) {
}
}
// Decompress 创建文件解压缩任务
func Decompress(c *gin.Context) {
var service explorer.ItemDecompressService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.CreateDecompressTask(c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
}
// AnonymousGetContent 匿名获取文件资源
func AnonymousGetContent(c *gin.Context) {
// 创建上下文

@ -269,6 +269,8 @@ func InitMasterRouter() *gin.Engine {
file.POST("archive", controllers.Archive)
// 创建文件压缩任务
file.POST("compress", controllers.Compress)
// 创建文件解压缩任务
file.POST("decompress", controllers.Decompress)
}
// 目录

@ -51,7 +51,7 @@ func (service *DirectoryService) CreateDirectory(c *gin.Context) serializer.Resp
defer cancel()
// 创建目录
err = fs.CreateDirectory(ctx, service.Path)
_, err = fs.CreateDirectory(ctx, service.Path)
if err != nil {
return serializer.Err(serializer.CodeCreateFolderFailed, err.Error(), err)
}

@ -15,6 +15,7 @@ import (
"math"
"net/url"
"path"
"strings"
"time"
)
@ -44,6 +45,59 @@ type ItemCompressService struct {
Name string `json:"name" binding:"required,min=1,max=255"`
}
// ItemDecompressService 文件解压缩任务服务
type ItemDecompressService struct {
Src string `json:"src" binding:"exists"`
Dst string `json:"dst" binding:"required,min=1,max=65535"`
}
// CreateDecompressTask 创建文件解压缩任务
func (service *ItemDecompressService) CreateDecompressTask(c *gin.Context) serializer.Response {
// 创建文件系统
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
defer fs.Recycle()
// 检查用户组权限
if !fs.User.Group.OptionsSerialized.ArchiveTask {
return serializer.Err(serializer.CodeGroupNotAllowed, "当前用户组无法进行此操作", nil)
}
// 存放目录是否存在
if exist, _ := fs.IsPathExist(service.Dst); !exist {
return serializer.Err(serializer.CodeNotFound, "存放路径不存在", nil)
}
// 压缩包是否存在
exist, file := fs.IsFileExist(service.Src)
if !exist {
return serializer.Err(serializer.CodeNotFound, "文件不存在", nil)
}
// 文件尺寸限制
if fs.User.Group.OptionsSerialized.DecompressSize != 0 && file.Size > fs.User.Group.
OptionsSerialized.DecompressSize {
return serializer.Err(serializer.CodeParamErr, "文件太大", nil)
}
// 必须是zip压缩包
if !strings.HasSuffix(file.Name, ".zip") {
return serializer.Err(serializer.CodeParamErr, "只能解压 ZIP 格式的压缩文件", nil)
}
// 创建任务
job, err := task.NewDecompressTask(fs.User, service.Src, service.Dst)
if err != nil {
return serializer.Err(serializer.CodeNotSet, "任务创建失败", err)
}
task.TaskPoll.Submit(job)
return serializer.Response{}
}
// CreateCompressTask 创建文件压缩任务
func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serializer.Response {
// 创建文件系统
@ -92,6 +146,12 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize
totalSize += files[i].Size
}
// 文件尺寸限制
if fs.User.Group.OptionsSerialized.DecompressSize != 0 && totalSize > fs.User.Group.
OptionsSerialized.CompressSize {
return serializer.Err(serializer.CodeParamErr, "文件太大", nil)
}
// 按照平均压缩率计算用户空间是否足够
compressRatio := 0.4
spaceNeeded := uint64(math.Round(float64(totalSize) * compressRatio))
@ -105,8 +165,8 @@ func (service *ItemCompressService) CreateCompressTask(c *gin.Context) serialize
if err != nil {
return serializer.Err(serializer.CodeNotSet, "任务创建失败", err)
}
task.TaskPoll.Submit(job)
return serializer.Response{}
}

Loading…
Cancel
Save