Feat: file download in service level

pull/247/head
HFO4 5 years ago
parent a734493b65
commit 4156a71adf

@ -17,6 +17,9 @@ type File struct {
FolderID uint `gorm:"index:folder_id"`
PolicyID uint
Dir string `gorm:"size:65536"`
// 关联模型
Policy Policy `gorm:"PRELOAD:false,association_autoupdate:false"`
}
// Create 创建文件记录
@ -41,3 +44,12 @@ func (folder *Folder) GetChildFile() ([]File, error) {
result := DB.Where("folder_id = ?", folder.ID).Find(&files)
return files, result.Error
}
// GetPolicy 获取文件所属策略
// TODO:test
func (file *File) GetPolicy() *Policy {
if file.Policy.Model.ID == 0 {
file.Policy, _ = GetPolicyByID(file.PolicyID)
}
return &file.Policy
}

@ -9,4 +9,6 @@ const (
SavePathCtx
// FileHeaderCtx 上传的文件
FileHeaderCtx
// PathCtx 文件或目录的虚拟路径
PathCtx
)

@ -1,6 +1,9 @@
package filesystem
import "errors"
import (
"errors"
"github.com/HFO4/cloudreve/pkg/serializer"
)
var (
ErrUnknownPolicyType = errors.New("未知存储策略类型")
@ -8,7 +11,8 @@ var (
ErrFileExtensionNotAllowed = errors.New("不允许上传此类型的文件")
ErrInsufficientCapacity = errors.New("容量空间不足")
ErrIllegalObjectName = errors.New("目标名称非法")
ErrInsertFileRecord = errors.New("无法插入文件记录")
ErrInsertFileRecord = serializer.NewError(serializer.CodeDBError, "无法插入文件记录", nil)
ErrFileExisted = errors.New("同名文件已存在")
ErrPathNotExist = errors.New("路径不存在")
ErrPathNotExist = serializer.NewError(404, "路径不存在", nil)
ErrObjectNotExist = serializer.NewError(404, "文件不存在", nil)
)

@ -5,6 +5,7 @@ import (
"errors"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"io"
)
@ -36,7 +37,28 @@ func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder) (*model
return &newFile, nil
}
// Download 处理下载文件请求
// Download 处理下载文件请求path为虚拟路径
// TODO:测试
func (fs *FileSystem) Download(ctx context.Context, path string) (io.ReadCloser, error) {
// 触发`下载前`钩子
err := fs.Trigger(ctx, fs.BeforeFileDownload)
if err != nil {
util.Log().Debug("BeforeFileDownload 钩子执行失败,%s", err)
return nil, err
}
// 找到文件
exist, file := fs.IsFileExist(path)
if !exist {
return nil, ErrObjectNotExist
}
// 将当前存储策略重设为文件使用的
fs.Policy = file.GetPolicy()
err = fs.dispatchHandler()
if err != nil {
return nil, err
}
return nil, serializer.NewError(serializer.CodeEncryptError, "人都的", errors.New("不是人都的"))
}

@ -29,10 +29,10 @@ type Handler interface {
// FileSystem 管理文件的文件系统
type FileSystem struct {
/*
*/
// 文件系统所有者
User *model.User
// 操作文件使用的上传策略
Policy *model.Policy
/*
@ -45,6 +45,8 @@ type FileSystem struct {
AfterValidateFailed []Hook
// 用户取消上传后
AfterUploadCanceled []Hook
// 文件下载前
BeforeFileDownload []Hook
/*
@ -54,21 +56,36 @@ type FileSystem struct {
// NewFileSystem 初始化一个文件系统
func NewFileSystem(user *model.User) (*FileSystem, error) {
var handler Handler
fs := &FileSystem{
User: user,
}
// 分配存储策略适配器
err := fs.dispatchHandler()
// TODO 分配默认钩子
return fs, err
}
// dispatchHandler 根据存储策略分配文件适配器
// TODO: 测试
func (fs *FileSystem) dispatchHandler() error {
var policyType string
if fs.Policy == nil {
// 如果没有具体指定,就是用用户当前存储策略
policyType = fs.User.Policy.Type
} else {
policyType = fs.Policy.Type
}
// 根据存储策略类型分配适配器
switch user.Policy.Type {
switch policyType {
case "local":
handler = local.Handler{}
fs.Handler = local.Handler{}
return nil
default:
return nil, ErrUnknownPolicyType
return ErrUnknownPolicyType
}
// TODO 分配默认钩子
return &FileSystem{
User: user,
Handler: handler,
}, nil
}
// NewFileSystemFromContext 从gin.Context创建文件系统

@ -21,6 +21,8 @@ func (fs *FileSystem) Use(name string, hook Hook) {
fs.AfterValidateFailed = append(fs.AfterValidateFailed, hook)
case "AfterUploadCanceled":
fs.AfterUploadCanceled = append(fs.AfterUploadCanceled, hook)
case "BeforeFileDownload":
fs.BeforeFileDownload = append(fs.BeforeFileDownload, hook)
}
}
@ -37,6 +39,15 @@ func (fs *FileSystem) Trigger(ctx context.Context, hooks []Hook) error {
return nil
}
// HookIsFileExist 检查虚拟路径文件是否存在
func HookIsFileExist(ctx context.Context, fs *FileSystem) error {
filePath := ctx.Value(PathCtx).(string)
if ok, _ := fs.IsFileExist(filePath); ok {
return nil
}
return ErrObjectNotExist
}
// HookValidateFile 一系列对文件检验的集合
func HookValidateFile(ctx context.Context, fs *FileSystem) error {
file := ctx.Value(FileHeaderCtx).(FileHeader)
@ -107,10 +118,10 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
}
// 检查文件是否存在
if fs.IsFileExist(path.Join(
if ok, _ := fs.IsFileExist(path.Join(
virtualPath,
ctx.Value(FileHeaderCtx).(FileHeader).GetFileName(),
)) {
)); ok {
return ErrFileExisted
}

@ -108,7 +108,7 @@ func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) erro
}
// 是否有同名文件
if fs.IsFileExist(path.Join(base, dir)) {
if ok, _ := fs.IsFileExist(path.Join(base, dir)); ok {
return ErrFileExisted
}
@ -133,11 +133,11 @@ func (fs *FileSystem) IsPathExist(path string) (bool, *model.Folder) {
}
// IsFileExist 返回给定路径的文件是否存在
func (fs *FileSystem) IsFileExist(fullPath string) bool {
func (fs *FileSystem) IsFileExist(fullPath string) (bool, model.File) {
basePath := path.Dir(fullPath)
fileName := path.Base(fullPath)
_, err := model.GetFileByPathAndName(basePath, fileName, fs.User.ID)
file, err := model.GetFileByPathAndName(basePath, fileName, fs.User.ID)
return err == nil
return err == nil, file
}

@ -21,7 +21,7 @@ func TestFileSystem_IsFileExist(t *testing.T) {
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/s", "1.txt").WillReturnRows(
sqlmock.NewRows([]string{"Name"}).AddRow("s"),
)
testResult := fs.IsFileExist("/s/1.txt")
testResult, _ := fs.IsFileExist("/s/1.txt")
asserts.True(testResult)
asserts.NoError(mock.ExpectationsWereMet())
@ -29,7 +29,7 @@ func TestFileSystem_IsFileExist(t *testing.T) {
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/ss/dfsd", "1.txt").WillReturnRows(
sqlmock.NewRows([]string{"Name"}),
)
testResult = fs.IsFileExist("/ss/dfsd/1.txt")
testResult, _ = fs.IsFileExist("/ss/dfsd/1.txt")
asserts.False(testResult)
asserts.NoError(mock.ExpectationsWereMet())
}

@ -44,7 +44,7 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
followUpErr := fs.Trigger(ctx, fs.AfterValidateFailed)
// 失败后再失败...
if followUpErr != nil {
util.Log().Warning("AfterValidateFailed 钩子执行失败,%s", followUpErr)
util.Log().Debug("AfterValidateFailed 钩子执行失败,%s", followUpErr)
}
return err
@ -85,7 +85,7 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHe
ctx = context.WithValue(ctx, SavePathCtx, path)
err := fs.Trigger(ctx, fs.AfterUploadCanceled)
if err != nil {
util.Log().Warning("执行 AfterUploadCanceled 钩子出错,%s", err)
util.Log().Debug("执行 AfterUploadCanceled 钩子出错,%s", err)
}
}
}

@ -18,9 +18,10 @@ func (service *FileDownloadService) Download(ctx context.Context, c *gin.Context
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
// 开始处理下载
ctx = context.WithValue(ctx, filesystem.GinCtx, c)
_, err = fs.Download(ctx, service.Path)
if err != nil {

Loading…
Cancel
Save