Cloudreve/pkg/filesystem/hooks.go

357 lines
9.6 KiB
Go
Raw Normal View History

2019-11-16 16:05:10 +08:00
package filesystem
2019-11-16 20:31:34 +08:00
import (
"context"
"errors"
2019-12-15 14:01:37 +08:00
"io/ioutil"
"strings"
"sync"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/local"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
2019-11-16 20:31:34 +08:00
)
2019-11-16 16:05:10 +08:00
2019-11-26 11:42:26 +08:00
// Hook 钩子函数
type Hook func(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error
2019-11-26 11:42:26 +08:00
// Use 注入钩子
func (fs *FileSystem) Use(name string, hook Hook) {
if fs.Hooks == nil {
fs.Hooks = make(map[string][]Hook)
2019-11-26 11:42:26 +08:00
}
if _, ok := fs.Hooks[name]; ok {
fs.Hooks[name] = append(fs.Hooks[name], hook)
return
}
fs.Hooks[name] = []Hook{hook}
2019-11-26 11:42:26 +08:00
}
2020-02-03 13:23:33 +08:00
// CleanHooks 清空钩子,name为空表示全部清空
func (fs *FileSystem) CleanHooks(name string) {
if name == "" {
fs.Hooks = nil
} else {
delete(fs.Hooks, name)
}
}
2019-11-26 11:42:26 +08:00
// Trigger 触发钩子,遇到第一个错误时
// 返回错误,后续钩子不会继续执行
func (fs *FileSystem) Trigger(ctx context.Context, name string, file fsctx.FileHeader) error {
if hooks, ok := fs.Hooks[name]; ok {
for _, hook := range hooks {
err := hook(ctx, fs, file)
if err != nil {
util.Log().Warning("钩子执行失败:%s", err)
return err
}
2019-11-26 11:42:26 +08:00
}
}
return nil
}
2019-12-27 21:15:05 +08:00
// HookSlaveUploadValidate Slave模式下对文件上传的一系列验证
func HookSlaveUploadValidate(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
2019-12-27 21:15:05 +08:00
policy := ctx.Value(fsctx.UploadPolicyCtx).(serializer.UploadPolicy)
fileInfo := file.Info()
2019-12-27 21:15:05 +08:00
// 验证单文件尺寸
2020-02-27 11:17:59 +08:00
if policy.MaxSize > 0 {
if fileInfo.Size > policy.MaxSize {
2020-02-27 11:17:59 +08:00
return ErrFileSizeTooBig
}
2019-12-27 21:15:05 +08:00
}
// 验证文件名
if !fs.ValidateLegalName(ctx, fileInfo.FileName) {
2019-12-27 21:15:05 +08:00
return ErrIllegalObjectName
}
// 验证扩展名
if len(policy.AllowedExtension) > 0 && !IsInExtensionList(policy.AllowedExtension, fileInfo.FileName) {
2019-12-27 21:15:05 +08:00
return ErrFileExtensionNotAllowed
}
return nil
}
2019-11-26 11:42:26 +08:00
// HookValidateFile 一系列对文件检验的集合
func HookValidateFile(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
fileInfo := file.Info()
2019-11-16 16:05:10 +08:00
// 验证单文件尺寸
if !fs.ValidateFileSize(ctx, fileInfo.Size) {
return ErrFileSizeTooBig
2019-11-16 16:05:10 +08:00
}
// 验证文件名
if !fs.ValidateLegalName(ctx, fileInfo.FileName) {
return ErrIllegalObjectName
}
2019-11-16 16:05:10 +08:00
// 验证扩展名
if !fs.ValidateExtension(ctx, fileInfo.FileName) {
return ErrFileExtensionNotAllowed
2019-11-17 13:50:14 +08:00
}
2019-11-26 11:42:26 +08:00
return nil
}
// HookResetPolicy 重设存储策略为上下文已有文件
func HookResetPolicy(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
2019-12-15 14:01:37 +08:00
originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
if !ok {
return ErrObjectNotExist
}
fs.Policy = originFile.GetPolicy()
return fs.DispatchHandler()
2019-12-15 14:01:37 +08:00
}
2019-11-26 11:42:26 +08:00
// HookValidateCapacity 验证并扣除用户容量,包含数据库操作
func HookValidateCapacity(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
2019-11-17 13:50:14 +08:00
// 验证并扣除容量
if !fs.ValidateCapacity(ctx, file.Info().Size) {
return ErrInsufficientCapacity
2019-11-16 16:05:10 +08:00
}
return nil
}
// HookValidateCapacityWithoutIncrease 验证用户容量,不扣除
func HookValidateCapacityWithoutIncrease(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
// 验证并扣除容量
if fs.User.GetRemainingCapacity() < file.Info().Size {
return ErrInsufficientCapacity
}
return nil
}
2019-12-15 14:01:37 +08:00
// HookChangeCapacity 根据原有文件和新文件的大小更新用户容量
func HookChangeCapacity(ctx context.Context, fs *FileSystem, newFile fsctx.FileHeader) error {
2019-12-15 14:01:37 +08:00
originFile := ctx.Value(fsctx.FileModelCtx).(model.File)
newFileSize := newFile.Info().Size
2019-12-15 14:01:37 +08:00
if newFileSize > originFile.Size {
if !fs.ValidateCapacity(ctx, newFileSize-originFile.Size) {
2019-12-15 14:01:37 +08:00
return ErrInsufficientCapacity
}
return nil
}
fs.User.DeductionStorage(originFile.Size - newFileSize)
2019-12-15 14:01:37 +08:00
return nil
}
2019-11-26 11:42:26 +08:00
// HookDeleteTempFile 删除已保存的临时文件
func HookDeleteTempFile(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
// 删除临时文件
_, err := fs.Handler.Delete(ctx, []string{file.Info().SavePath})
2020-01-17 10:52:43 +08:00
if err != nil {
util.Log().Warning("无法清理上传临时文件,%s", err)
}
2019-11-26 11:42:26 +08:00
return nil
}
2019-12-15 14:01:37 +08:00
// HookCleanFileContent 清空文件内容
func HookCleanFileContent(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
2019-12-15 14:01:37 +08:00
// 清空内容
return fs.Handler.Put(ctx, &fsctx.FileStream{
File: ioutil.NopCloser(strings.NewReader("")),
SavePath: file.Info().SavePath,
Size: 0,
})
2019-12-15 14:01:37 +08:00
}
// HookClearFileSize 将原始文件的尺寸设为0
func HookClearFileSize(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
2019-12-15 14:01:37 +08:00
originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
if !ok {
return ErrObjectNotExist
}
return originFile.UpdateSize(0)
}
// HookCancelContext 取消上下文
func HookCancelContext(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
cancelFunc, ok := ctx.Value(fsctx.CancelFuncCtx).(context.CancelFunc)
if ok {
cancelFunc()
}
return nil
}
2019-11-26 11:42:26 +08:00
// HookGiveBackCapacity 归还用户容量
func HookGiveBackCapacity(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
once, ok := ctx.Value(fsctx.ValidateCapacityOnceCtx).(*sync.Once)
if !ok {
once = &sync.Once{}
}
2019-11-26 11:42:26 +08:00
// 归还用户容量
res := true
once.Do(func() {
res = fs.User.DeductionStorage(file.Info().Size)
})
if !res {
return errors.New("无法继续降低用户已用存储")
}
return nil
}
2019-11-18 19:09:56 +08:00
// HookUpdateSourceName 更新文件SourceName
// TODO测试
func HookUpdateSourceName(ctx context.Context, fs *FileSystem, file fsctx.FileHeader) error {
originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
if !ok {
return ErrObjectNotExist
}
return originFile.UpdateSourceName(originFile.SourceName)
}
2019-12-15 14:01:37 +08:00
// GenericAfterUpdate 文件内容更新后
func GenericAfterUpdate(ctx context.Context, fs *FileSystem, newFile fsctx.FileHeader) error {
2019-12-15 14:01:37 +08:00
// 更新文件尺寸
originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
if !ok {
return ErrObjectNotExist
}
newFile.SetModel(&originFile)
err := originFile.UpdateSize(newFile.Info().Size)
2019-12-15 14:01:37 +08:00
if err != nil {
return err
}
return nil
}
2019-12-28 13:14:00 +08:00
// SlaveAfterUpload Slave模式下上传完成钩子
func SlaveAfterUpload(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
2019-12-29 17:04:08 +08:00
policy := ctx.Value(fsctx.UploadPolicyCtx).(serializer.UploadPolicy)
fileInfo := fileHeader.Info()
2019-12-29 17:04:08 +08:00
2019-12-28 13:14:00 +08:00
// 构造一个model.File用于生成缩略图
file := model.File{
Name: fileInfo.FileName,
SourceName: fileInfo.SavePath,
2019-12-28 13:14:00 +08:00
}
fs.GenerateThumbnail(ctx, &file)
2020-01-04 19:44:38 +08:00
if policy.CallbackURL == "" {
return nil
}
2019-12-29 17:04:08 +08:00
// 发送回调请求
2020-01-15 10:14:15 +08:00
callbackBody := serializer.UploadCallback{
2019-12-29 17:04:08 +08:00
Name: file.Name,
SourceName: file.SourceName,
PicInfo: file.PicInfo,
Size: fileInfo.Size,
2019-12-29 17:04:08 +08:00
}
return request.RemoteCallback(policy.CallbackURL, callbackBody)
2019-12-28 13:14:00 +08:00
}
2019-11-18 19:09:56 +08:00
// GenericAfterUpload 文件上传完成后,包含数据库操作
func GenericAfterUpload(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
fileInfo := fileHeader.Info()
2019-11-18 19:09:56 +08:00
2020-02-03 13:23:33 +08:00
// 检查路径是否存在,不存在就创建
isExist, folder := fs.IsPathExist(fileInfo.VirtualPath)
if !isExist {
newFolder, err := fs.CreateDirectory(ctx, fileInfo.VirtualPath)
2020-02-03 13:23:33 +08:00
if err != nil {
return err
}
folder = newFolder
2019-11-18 19:09:56 +08:00
}
// 检查文件是否存在
if ok, file := fs.IsChildFileExist(
folder,
fileInfo.FileName,
); ok {
if file.UploadSessionID != nil {
return ErrFileUploadSessionExisted
}
return ErrFileExisted
}
// 向数据库中插入记录
file, err := fs.AddFile(ctx, folder, fileHeader)
if err != nil {
return ErrInsertFileRecord
}
fileHeader.SetModel(file)
return nil
}
// HookGenerateThumb 生成缩略图
func HookGenerateThumb(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
2019-11-20 15:24:26 +08:00
// 异步尝试生成缩略图
fileMode := fileHeader.Info().Model.(*model.File)
if fs.Policy.IsThumbGenerateNeeded() {
fs.recycleLock.Lock()
go func() {
defer fs.recycleLock.Unlock()
_, _ = fs.Handler.Delete(ctx, []string{fileMode.SourceName + conf.ThumbConfig.FileSuffix})
fs.GenerateThumbnail(ctx, fileMode)
}()
2020-01-17 10:52:43 +08:00
}
2019-11-18 19:09:56 +08:00
return nil
}
// HookClearFileHeaderSize 将FileHeader大小设定为0
func HookClearFileHeaderSize(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
fileHeader.SetSize(0)
return nil
}
// HookTruncateFileTo 将物理文件截断至 size
func HookTruncateFileTo(size uint64) Hook {
return func(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
if fs.Policy.Type == "local" {
if driver, ok := fs.Handler.(local.Driver); ok {
return driver.Truncate(ctx, fileHeader.Info().SavePath, size)
}
}
return nil
}
}
// HookChunkUploadFinished 单个分片上传结束后
func HookChunkUploaded(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
fileInfo := fileHeader.Info()
// 更新文件大小
return fileInfo.Model.(*model.File).UpdateSize(fileInfo.Model.(*model.File).GetSize() + fileInfo.Size)
}
// HookChunkUploadFinished 分片上传结束后处理文件
func HookChunkUploadFinished(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
fileInfo := fileHeader.Info()
fileModel := fileInfo.Model.(*model.File)
return fileModel.PopChunkToFile(fileInfo.LastModified)
}
// HookChunkUploadFinished 分片上传结束后处理文件
func HookDeleteUploadSession(id string) Hook {
return func(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
cache.Deletes([]string{id}, UploadSessionCachePrefix)
return nil
}
}