|
|
|
package filesystem
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
model "github.com/HFO4/cloudreve/models"
|
|
|
|
"github.com/HFO4/cloudreve/pkg/cache"
|
|
|
|
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
|
|
|
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
|
|
|
"github.com/HFO4/cloudreve/pkg/serializer"
|
|
|
|
"github.com/HFO4/cloudreve/pkg/util"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"path"
|
|
|
|
)
|
|
|
|
|
|
|
|
/* ================
|
|
|
|
上传处理相关
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Upload 上传文件
|
|
|
|
func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
|
|
|
|
ctx = context.WithValue(ctx, fsctx.FileHeaderCtx, file)
|
|
|
|
|
|
|
|
// 上传前的钩子
|
|
|
|
err = fs.Trigger(ctx, "BeforeUpload")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 生成文件名和路径,
|
|
|
|
var savePath string
|
|
|
|
// 如果是更新操作就从上下文中获取
|
|
|
|
if originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File); ok {
|
|
|
|
savePath = originFile.SourceName
|
|
|
|
} else {
|
|
|
|
savePath = fs.GenerateSavePath(ctx, file)
|
|
|
|
}
|
|
|
|
ctx = context.WithValue(ctx, fsctx.SavePathCtx, savePath)
|
|
|
|
|
|
|
|
// 处理客户端未完成上传时,关闭连接
|
|
|
|
go fs.CancelUpload(ctx, savePath, file)
|
|
|
|
|
|
|
|
// 保存文件
|
|
|
|
err = fs.Handler.Put(ctx, file, savePath, file.GetSize())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 上传完成后的钩子
|
|
|
|
err = fs.Trigger(ctx, "AfterUpload")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
// 上传完成后续处理失败
|
|
|
|
followUpErr := fs.Trigger(ctx, "AfterValidateFailed")
|
|
|
|
// 失败后再失败...
|
|
|
|
if followUpErr != nil {
|
|
|
|
util.Log().Debug("AfterValidateFailed 钩子执行失败,%s", followUpErr)
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
util.Log().Info(
|
|
|
|
"新文件PUT:%s , 大小:%d, 上传者:%s",
|
|
|
|
file.GetFileName(),
|
|
|
|
file.GetSize(),
|
|
|
|
fs.User.Nick,
|
|
|
|
)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GenerateSavePath 生成要存放文件的路径
|
|
|
|
// TODO 完善测试
|
|
|
|
func (fs *FileSystem) GenerateSavePath(ctx context.Context, file FileHeader) string {
|
|
|
|
if fs.User.Model.ID != 0 {
|
|
|
|
return path.Join(
|
|
|
|
fs.User.Policy.GeneratePath(
|
|
|
|
fs.User.Model.ID,
|
|
|
|
file.GetVirtualPath(),
|
|
|
|
),
|
|
|
|
fs.User.Policy.GenerateFileName(
|
|
|
|
fs.User.Model.ID,
|
|
|
|
file.GetFileName(),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 匿名文件系统尝试根据上下文中的上传策略生成路径
|
|
|
|
var anonymousPolicy model.Policy
|
|
|
|
if policy, ok := ctx.Value(fsctx.UploadPolicyCtx).(serializer.UploadPolicy); ok {
|
|
|
|
anonymousPolicy = model.Policy{
|
|
|
|
Type: "remote",
|
|
|
|
AutoRename: policy.AutoRename,
|
|
|
|
DirNameRule: policy.SavePath,
|
|
|
|
FileNameRule: policy.FileName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path.Join(
|
|
|
|
anonymousPolicy.GeneratePath(
|
|
|
|
0,
|
|
|
|
"",
|
|
|
|
),
|
|
|
|
anonymousPolicy.GenerateFileName(
|
|
|
|
0,
|
|
|
|
file.GetFileName(),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CancelUpload 监测客户端取消上传
|
|
|
|
func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHeader) {
|
|
|
|
var reqContext context.Context
|
|
|
|
if ginCtx, ok := ctx.Value(fsctx.GinCtx).(*gin.Context); ok {
|
|
|
|
reqContext = ginCtx.Request.Context()
|
|
|
|
} else {
|
|
|
|
reqContext = ctx.Value(fsctx.HTTPCtx).(context.Context)
|
|
|
|
}
|
|
|
|
defer fs.Recycle()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-reqContext.Done():
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
// 客户端正常关闭,不执行操作
|
|
|
|
default:
|
|
|
|
// 客户端取消上传,删除临时文件
|
|
|
|
util.Log().Debug("客户端取消上传")
|
|
|
|
if fs.Hooks["AfterUploadCanceled"] == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx = context.WithValue(ctx, fsctx.SavePathCtx, path)
|
|
|
|
err := fs.Trigger(ctx, "AfterUploadCanceled")
|
|
|
|
if err != nil {
|
|
|
|
util.Log().Debug("执行 AfterUploadCanceled 钩子出错,%s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUploadToken 生成新的上传凭证
|
|
|
|
func (fs *FileSystem) GetUploadToken(ctx context.Context, path string, size uint64) (*serializer.UploadCredential, error) {
|
|
|
|
// 获取相关有效期设置
|
|
|
|
credentialTTL := model.GetIntSetting("upload_credential_timeout", 3600)
|
|
|
|
callBackSessionTTL := model.GetIntSetting("upload_session_timeout", 86400)
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// 是否需要预先生成存储路径
|
|
|
|
if fs.User.Policy.IsPathGenerateNeeded() {
|
|
|
|
ctx = context.WithValue(ctx, fsctx.SavePathCtx, fs.GenerateSavePath(ctx, local.FileStream{}))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 获取上传凭证
|
|
|
|
callbackKey := util.RandStringRunes(32)
|
|
|
|
credential, err := fs.Handler.Token(ctx, int64(credentialTTL), callbackKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, serializer.NewError(serializer.CodeEncryptError, "无法获取上传凭证", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 创建回调会话
|
|
|
|
err = cache.Set(
|
|
|
|
"callback_"+callbackKey,
|
|
|
|
serializer.UploadSession{
|
|
|
|
UID: fs.User.ID,
|
|
|
|
PolicyID: fs.User.GetPolicyID(),
|
|
|
|
VirtualPath: path,
|
|
|
|
},
|
|
|
|
callBackSessionTTL,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &credential, nil
|
|
|
|
}
|