Feat: recycling file storage and user capacity when uploading canceled
This commit is contained in:
parent
160f964564
commit
631c23f065
9 changed files with 93 additions and 14 deletions
|
@ -54,8 +54,18 @@ type UserOption struct {
|
||||||
WebDAVKey string `json:"webdav_key"`
|
WebDAVKey string `json:"webdav_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeductionCapacity 扣除用户容量配额
|
// DeductionStorage 减少用户已用容量
|
||||||
func (user *User) DeductionCapacity(size uint64) bool {
|
func (user *User) DeductionStorage(size uint64) bool {
|
||||||
|
if size <= user.Storage {
|
||||||
|
user.Storage -= size
|
||||||
|
DB.Save(user)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncreaseStorage 检查并增加用户已用容量
|
||||||
|
func (user *User) IncreaseStorage(size uint64) bool {
|
||||||
if size <= user.GetRemainingCapacity() {
|
if size <= user.GetRemainingCapacity() {
|
||||||
user.Storage += size
|
user.Storage += size
|
||||||
DB.Save(user)
|
DB.Save(user)
|
||||||
|
|
|
@ -204,15 +204,15 @@ func TestUser_DeductionCapacity(t *testing.T) {
|
||||||
asserts.NoError(err)
|
asserts.NoError(err)
|
||||||
asserts.NoError(mock.ExpectationsWereMet())
|
asserts.NoError(mock.ExpectationsWereMet())
|
||||||
|
|
||||||
asserts.Equal(false, newUser.DeductionCapacity(101))
|
asserts.Equal(false, newUser.IncreaseStorage(101))
|
||||||
asserts.Equal(uint64(0), newUser.Storage)
|
asserts.Equal(uint64(0), newUser.Storage)
|
||||||
|
|
||||||
asserts.Equal(true, newUser.DeductionCapacity(1))
|
asserts.Equal(true, newUser.IncreaseStorage(1))
|
||||||
asserts.Equal(uint64(1), newUser.Storage)
|
asserts.Equal(uint64(1), newUser.Storage)
|
||||||
|
|
||||||
asserts.Equal(true, newUser.DeductionCapacity(99))
|
asserts.Equal(true, newUser.IncreaseStorage(99))
|
||||||
asserts.Equal(uint64(100), newUser.Storage)
|
asserts.Equal(uint64(100), newUser.Storage)
|
||||||
|
|
||||||
asserts.Equal(false, newUser.DeductionCapacity(1))
|
asserts.Equal(false, newUser.IncreaseStorage(1))
|
||||||
asserts.Equal(uint64(100), newUser.Storage)
|
asserts.Equal(uint64(100), newUser.Storage)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/HFO4/cloudreve/models"
|
"github.com/HFO4/cloudreve/models"
|
||||||
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -21,7 +22,10 @@ type FileData interface {
|
||||||
|
|
||||||
// Handler 存储策略适配器
|
// Handler 存储策略适配器
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
|
// 上传文件
|
||||||
Put(ctx context.Context, file io.ReadCloser, dst string) error
|
Put(ctx context.Context, file io.ReadCloser, dst string) error
|
||||||
|
// 删除一个或多个文件
|
||||||
|
Delete(ctx context.Context, files []string) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileSystem 管理文件的文件系统
|
// FileSystem 管理文件的文件系统
|
||||||
|
@ -39,7 +43,9 @@ type FileSystem struct {
|
||||||
// 上传文件后
|
// 上传文件后
|
||||||
AfterUpload func(ctx context.Context, fs *FileSystem) error
|
AfterUpload func(ctx context.Context, fs *FileSystem) error
|
||||||
// 文件保存成功,插入数据库验证失败后
|
// 文件保存成功,插入数据库验证失败后
|
||||||
ValidateFailed func(ctx context.Context, fs *FileSystem) error
|
AfterValidateFailed func(ctx context.Context, fs *FileSystem) error
|
||||||
|
// 用户取消上传后
|
||||||
|
AfterUploadCanceled func(ctx context.Context, fs *FileSystem, file FileData) error
|
||||||
|
|
||||||
/*
|
/*
|
||||||
文件系统处理适配器
|
文件系统处理适配器
|
||||||
|
@ -73,9 +79,11 @@ func NewFileSystem(user *model.User) (*FileSystem, error) {
|
||||||
// Upload 上传文件
|
// Upload 上传文件
|
||||||
func (fs *FileSystem) Upload(ctx context.Context, file FileData) (err error) {
|
func (fs *FileSystem) Upload(ctx context.Context, file FileData) (err error) {
|
||||||
// 上传前的钩子
|
// 上传前的钩子
|
||||||
err = fs.BeforeUpload(ctx, fs, file)
|
if fs.BeforeUpload != nil {
|
||||||
if err != nil {
|
err = fs.BeforeUpload(ctx, fs, file)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成文件名和路径
|
// 生成文件名和路径
|
||||||
|
@ -106,10 +114,17 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileDa
|
||||||
ginCtx := ctx.Value("ginCtx").(*gin.Context)
|
ginCtx := ctx.Value("ginCtx").(*gin.Context)
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
fmt.Println("正常关闭")
|
||||||
// 客户端正常关闭,不执行操作
|
// 客户端正常关闭,不执行操作
|
||||||
case <-ginCtx.Request.Context().Done():
|
case <-ginCtx.Request.Context().Done():
|
||||||
// 客户端取消了上传,删除保存的文件
|
// 客户端取消了上传
|
||||||
fmt.Println("取消上传")
|
if fs.AfterUploadCanceled == nil {
|
||||||
// 归还空间
|
return
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, "path", path)
|
||||||
|
err := fs.AfterUploadCanceled(ctx, fs, file)
|
||||||
|
if err != nil {
|
||||||
|
util.Log().Warning("执行 AfterUploadCanceled 钩子出错,%s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenericBeforeUpload 通用上传前处理钩子,包含数据库操作
|
// GenericBeforeUpload 通用上传前处理钩子,包含数据库操作
|
||||||
|
@ -27,3 +28,19 @@ func GenericBeforeUpload(ctx context.Context, fs *FileSystem, file FileData) err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenericAfterUploadCanceled 通用上传取消处理钩子,包含数据库操作
|
||||||
|
func GenericAfterUploadCanceled(ctx context.Context, fs *FileSystem, file FileData) error {
|
||||||
|
filePath := ctx.Value("path").(string)
|
||||||
|
// 删除临时文件
|
||||||
|
_, err := fs.Handler.Delete(ctx, []string{filePath})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 归还用户容量
|
||||||
|
if !fs.User.DeductionStorage(file.GetSize()) {
|
||||||
|
return errors.New("无法继续降低用户已用存储")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -36,4 +36,6 @@ func TestGenericBeforeUpload(t *testing.T) {
|
||||||
asserts.Error(GenericBeforeUpload(ctx, &fs, file))
|
asserts.Error(GenericBeforeUpload(ctx, &fs, file))
|
||||||
file.Name = "1.txt"
|
file.Name = "1.txt"
|
||||||
asserts.NoError(GenericBeforeUpload(ctx, &fs, file))
|
asserts.NoError(GenericBeforeUpload(ctx, &fs, file))
|
||||||
|
file.Name = "1.t/xt"
|
||||||
|
asserts.Error(GenericBeforeUpload(ctx, &fs, file))
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ func (handler Handler) Put(ctx context.Context, file io.ReadCloser, dst string)
|
||||||
if !util.Exists(basePath) {
|
if !util.Exists(basePath) {
|
||||||
err := os.MkdirAll(basePath, 0700)
|
err := os.MkdirAll(basePath, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
util.Log().Warning("无法创建目录,%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +28,7 @@ func (handler Handler) Put(ctx context.Context, file io.ReadCloser, dst string)
|
||||||
// 创建目标文件
|
// 创建目标文件
|
||||||
out, err := os.Create(dst)
|
out, err := os.Create(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
util.Log().Warning("无法创建文件,%s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
|
@ -35,3 +37,22 @@ func (handler Handler) Put(ctx context.Context, file io.ReadCloser, dst string)
|
||||||
_, err = io.Copy(out, file)
|
_, err = io.Copy(out, file)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete 删除一个或多个文件,
|
||||||
|
// 返回已删除的文件,及遇到的最后一个错误
|
||||||
|
func (handler Handler) Delete(ctx context.Context, files []string) ([]string, error) {
|
||||||
|
deleted := make([]string, 0, len(files))
|
||||||
|
var retErr error
|
||||||
|
|
||||||
|
for _, value := range files {
|
||||||
|
err := os.Remove(value)
|
||||||
|
if err == nil {
|
||||||
|
deleted = append(deleted, value)
|
||||||
|
util.Log().Warning("无法删除文件,%s", err)
|
||||||
|
} else {
|
||||||
|
retErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleted, retErr
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ func (fs *FileSystem) ValidateFileSize(ctx context.Context, size uint64) bool {
|
||||||
|
|
||||||
// ValidateCapacity 验证并扣除用户容量
|
// ValidateCapacity 验证并扣除用户容量
|
||||||
func (fs *FileSystem) ValidateCapacity(ctx context.Context, size uint64) bool {
|
func (fs *FileSystem) ValidateCapacity(ctx context.Context, size uint64) bool {
|
||||||
if fs.User.DeductionCapacity(size) {
|
if fs.User.IncreaseStorage(size) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -25,6 +25,19 @@ func TestMain(m *testing.M) {
|
||||||
m.Run()
|
m.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileSystem_ValidateLegalName(t *testing.T) {
|
||||||
|
asserts := assert.New(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
fs := FileSystem{}
|
||||||
|
asserts.True(fs.ValidateLegalName(ctx, "1.txt"))
|
||||||
|
asserts.True(fs.ValidateLegalName(ctx, "1-1.txt"))
|
||||||
|
asserts.True(fs.ValidateLegalName(ctx, "1?1.txt"))
|
||||||
|
asserts.False(fs.ValidateLegalName(ctx, "1:1.txt"))
|
||||||
|
asserts.False(fs.ValidateLegalName(ctx, "../11.txt"))
|
||||||
|
asserts.False(fs.ValidateLegalName(ctx, "/11.txt"))
|
||||||
|
asserts.False(fs.ValidateLegalName(ctx, "\\11.txt"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileSystem_ValidateCapacity(t *testing.T) {
|
func TestFileSystem_ValidateCapacity(t *testing.T) {
|
||||||
asserts := assert.New(t)
|
asserts := assert.New(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
|
@ -70,6 +70,7 @@ func FileUploadStream(c *gin.Context) {
|
||||||
|
|
||||||
// 给文件系统分配钩子
|
// 给文件系统分配钩子
|
||||||
fs.BeforeUpload = filesystem.GenericBeforeUpload
|
fs.BeforeUpload = filesystem.GenericBeforeUpload
|
||||||
|
fs.AfterUploadCanceled = filesystem.GenericAfterUploadCanceled
|
||||||
|
|
||||||
// 执行上传
|
// 执行上传
|
||||||
uploadCtx := context.WithValue(ctx, "ginCtx", c)
|
uploadCtx := context.WithValue(ctx, "ginCtx", c)
|
||||||
|
|
Loading…
Add table
Reference in a new issue