mirror of https://github.com/cloudreve/Cloudreve
Modify: re-organize structure of filesystem
parent
e09294d388
commit
438ce02420
|
@ -1,6 +1,9 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import "github.com/jinzhu/gorm"
|
import (
|
||||||
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
// File 文件
|
// File 文件
|
||||||
type File struct {
|
type File struct {
|
||||||
|
@ -19,6 +22,7 @@ type File struct {
|
||||||
// Create 创建文件记录
|
// Create 创建文件记录
|
||||||
func (file *File) Create() (uint, error) {
|
func (file *File) Create() (uint, error) {
|
||||||
if err := DB.Create(file).Error; err != nil {
|
if err := DB.Create(file).Error; err != nil {
|
||||||
|
util.Log().Warning("无法插入文件记录, %s", err)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return file.ID, nil
|
return file.ID, nil
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
model "github.com/HFO4/cloudreve/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* ============
|
||||||
|
文件相关
|
||||||
|
============
|
||||||
|
*/
|
||||||
|
|
||||||
|
// AddFile 新增文件记录
|
||||||
|
func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder) (*model.File, error) {
|
||||||
|
file := ctx.Value(FileHeaderCtx).(FileHeader)
|
||||||
|
filePath := ctx.Value(SavePathCtx).(string)
|
||||||
|
|
||||||
|
newFile := model.File{
|
||||||
|
Name: file.GetFileName(),
|
||||||
|
SourceName: filePath,
|
||||||
|
UserID: fs.User.ID,
|
||||||
|
Size: file.GetSize(),
|
||||||
|
FolderID: parent.ID,
|
||||||
|
PolicyID: fs.User.Policy.ID,
|
||||||
|
Dir: parent.PositionAbsolute,
|
||||||
|
}
|
||||||
|
_, err := newFile.Create()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &newFile, nil
|
||||||
|
}
|
|
@ -4,11 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"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"
|
|
||||||
"io"
|
"io"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileHeader 上传来的文件数据处理器
|
// FileHeader 上传来的文件数据处理器
|
||||||
|
@ -32,12 +28,12 @@ type Handler interface {
|
||||||
// FileSystem 管理文件的文件系统
|
// FileSystem 管理文件的文件系统
|
||||||
type FileSystem struct {
|
type FileSystem struct {
|
||||||
/*
|
/*
|
||||||
文件系统所有者
|
文件系统所有者
|
||||||
*/
|
*/
|
||||||
User *model.User
|
User *model.User
|
||||||
|
|
||||||
/*
|
/*
|
||||||
钩子函数
|
钩子函数
|
||||||
*/
|
*/
|
||||||
// 上传文件前
|
// 上传文件前
|
||||||
BeforeUpload func(ctx context.Context, fs *FileSystem) error
|
BeforeUpload func(ctx context.Context, fs *FileSystem) error
|
||||||
|
@ -49,7 +45,7 @@ type FileSystem struct {
|
||||||
AfterUploadCanceled func(ctx context.Context, fs *FileSystem) error
|
AfterUploadCanceled func(ctx context.Context, fs *FileSystem) error
|
||||||
|
|
||||||
/*
|
/*
|
||||||
文件系统处理适配器
|
文件系统处理适配器
|
||||||
*/
|
*/
|
||||||
Handler Handler
|
Handler Handler
|
||||||
}
|
}
|
||||||
|
@ -72,136 +68,3 @@ func NewFileSystem(user *model.User) (*FileSystem, error) {
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============
|
|
||||||
文件相关
|
|
||||||
============
|
|
||||||
*/
|
|
||||||
|
|
||||||
// AddFile 新增文件记录
|
|
||||||
func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder) (*model.File, error) {
|
|
||||||
file := ctx.Value(FileHeaderCtx).(FileHeader)
|
|
||||||
filePath := ctx.Value(SavePathCtx).(string)
|
|
||||||
|
|
||||||
newFile := model.File{
|
|
||||||
Name: file.GetFileName(),
|
|
||||||
SourceName: filePath,
|
|
||||||
UserID: fs.User.ID,
|
|
||||||
Size: file.GetSize(),
|
|
||||||
FolderID: parent.ID,
|
|
||||||
PolicyID: fs.User.Policy.ID,
|
|
||||||
Dir: parent.PositionAbsolute,
|
|
||||||
}
|
|
||||||
_, err := newFile.Create()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &newFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================
|
|
||||||
上传处理相关
|
|
||||||
================
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Upload 上传文件
|
|
||||||
func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
|
|
||||||
ctx = context.WithValue(ctx, FileHeaderCtx, file)
|
|
||||||
|
|
||||||
// 上传前的钩子
|
|
||||||
if fs.BeforeUpload != nil {
|
|
||||||
err = fs.BeforeUpload(ctx, fs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成文件名和路径
|
|
||||||
savePath := fs.GenerateSavePath(ctx, file)
|
|
||||||
|
|
||||||
// 处理客户端未完成上传时,关闭连接
|
|
||||||
go fs.CancelUpload(ctx, savePath, file)
|
|
||||||
|
|
||||||
// 保存文件
|
|
||||||
err = fs.Handler.Put(ctx, file, savePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传完成后的钩子
|
|
||||||
if fs.AfterUpload != nil {
|
|
||||||
ctx = context.WithValue(ctx, SavePathCtx, savePath)
|
|
||||||
err = fs.AfterUpload(ctx, fs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// 上传完成后续处理失败
|
|
||||||
if fs.AfterValidateFailed != nil {
|
|
||||||
followUpErr := fs.AfterValidateFailed(ctx, fs)
|
|
||||||
// 失败后再失败...
|
|
||||||
if followUpErr != nil {
|
|
||||||
util.Log().Warning("AfterValidateFailed 钩子执行失败,%s", followUpErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSavePath 生成要存放文件的路径
|
|
||||||
func (fs *FileSystem) GenerateSavePath(ctx context.Context, file FileHeader) string {
|
|
||||||
return filepath.Join(
|
|
||||||
fs.User.Policy.GeneratePath(
|
|
||||||
fs.User.Model.ID,
|
|
||||||
file.GetVirtualPath(),
|
|
||||||
),
|
|
||||||
fs.User.Policy.GenerateFileName(
|
|
||||||
fs.User.Model.ID,
|
|
||||||
file.GetFileName(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CancelUpload 监测客户端取消上传
|
|
||||||
func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHeader) {
|
|
||||||
ginCtx := ctx.Value(GinCtx).(*gin.Context)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
// 客户端正常关闭,不执行操作
|
|
||||||
case <-ginCtx.Request.Context().Done():
|
|
||||||
// 客户端取消了上传
|
|
||||||
if fs.AfterUploadCanceled == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx = context.WithValue(ctx, SavePathCtx, path)
|
|
||||||
err := fs.AfterUploadCanceled(ctx, fs)
|
|
||||||
if err != nil {
|
|
||||||
util.Log().Warning("执行 AfterUploadCanceled 钩子出错,%s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================
|
|
||||||
路径/目录相关
|
|
||||||
=================
|
|
||||||
*/
|
|
||||||
|
|
||||||
// IsPathExist 返回给定目录是否存在
|
|
||||||
// 如果存在就返回目录
|
|
||||||
func (fs *FileSystem) IsPathExist(path string) (bool, model.Folder) {
|
|
||||||
folder, err := model.GetFolderByPath(path, fs.User.ID)
|
|
||||||
return err == nil, folder
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFileExist 返回给定路径的文件是否存在
|
|
||||||
func (fs *FileSystem) IsFileExist(fullPath string) bool {
|
|
||||||
basePath := path.Dir(fullPath)
|
|
||||||
fileName := path.Base(fullPath)
|
|
||||||
|
|
||||||
_, err := model.GetFileByPathAndName(basePath, fileName, fs.User.ID)
|
|
||||||
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -73,12 +73,15 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向数据库中插入记录
|
// 向数据库中插入记录
|
||||||
_, err := fs.AddFile(ctx, &folder)
|
file, err := fs.AddFile(ctx, &folder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("无法插入文件记录")
|
return errors.New("无法插入文件记录")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 是否需要立即获取图像大小?
|
||||||
|
|
||||||
// 异步尝试生成缩略图
|
// 异步尝试生成缩略图
|
||||||
|
go fs.GenerateThumbnail(ctx, file)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
model "github.com/HFO4/cloudreve/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* ===============
|
||||||
|
图像处理相关
|
||||||
|
===============
|
||||||
|
*/
|
||||||
|
|
||||||
|
// HandledExtension 可以生成缩略图的文件扩展名
|
||||||
|
var HandledExtension = []string{}
|
||||||
|
|
||||||
|
// GenerateThumbnail 尝试为文件生成缩略图
|
||||||
|
func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
|
||||||
|
// TODO
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
model "github.com/HFO4/cloudreve/models"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* =================
|
||||||
|
路径/目录相关
|
||||||
|
=================
|
||||||
|
*/
|
||||||
|
|
||||||
|
// IsPathExist 返回给定目录是否存在
|
||||||
|
// 如果存在就返回目录
|
||||||
|
func (fs *FileSystem) IsPathExist(path string) (bool, model.Folder) {
|
||||||
|
folder, err := model.GetFolderByPath(path, fs.User.ID)
|
||||||
|
return err == nil, folder
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFileExist 返回给定路径的文件是否存在
|
||||||
|
func (fs *FileSystem) IsFileExist(fullPath string) bool {
|
||||||
|
basePath := path.Dir(fullPath)
|
||||||
|
fileName := path.Base(fullPath)
|
||||||
|
|
||||||
|
_, err := model.GetFileByPathAndName(basePath, fileName, fs.User.ID)
|
||||||
|
|
||||||
|
return err == nil
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package filesystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* ================
|
||||||
|
上传处理相关
|
||||||
|
================
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Upload 上传文件
|
||||||
|
func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
|
||||||
|
ctx = context.WithValue(ctx, FileHeaderCtx, file)
|
||||||
|
|
||||||
|
// 上传前的钩子
|
||||||
|
if fs.BeforeUpload != nil {
|
||||||
|
err = fs.BeforeUpload(ctx, fs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成文件名和路径
|
||||||
|
savePath := fs.GenerateSavePath(ctx, file)
|
||||||
|
|
||||||
|
// 处理客户端未完成上传时,关闭连接
|
||||||
|
go fs.CancelUpload(ctx, savePath, file)
|
||||||
|
|
||||||
|
// 保存文件
|
||||||
|
err = fs.Handler.Put(ctx, file, savePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传完成后的钩子
|
||||||
|
if fs.AfterUpload != nil {
|
||||||
|
ctx = context.WithValue(ctx, SavePathCtx, savePath)
|
||||||
|
err = fs.AfterUpload(ctx, fs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// 上传完成后续处理失败
|
||||||
|
if fs.AfterValidateFailed != nil {
|
||||||
|
followUpErr := fs.AfterValidateFailed(ctx, fs)
|
||||||
|
// 失败后再失败...
|
||||||
|
if followUpErr != nil {
|
||||||
|
util.Log().Warning("AfterValidateFailed 钩子执行失败,%s", followUpErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.Log().Info("新文件上传:%s , 大小:%d, 上传者:%s", file.GetFileName(), file.GetSize(), fs.User.Nick)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSavePath 生成要存放文件的路径
|
||||||
|
func (fs *FileSystem) GenerateSavePath(ctx context.Context, file FileHeader) string {
|
||||||
|
return filepath.Join(
|
||||||
|
fs.User.Policy.GeneratePath(
|
||||||
|
fs.User.Model.ID,
|
||||||
|
file.GetVirtualPath(),
|
||||||
|
),
|
||||||
|
fs.User.Policy.GenerateFileName(
|
||||||
|
fs.User.Model.ID,
|
||||||
|
file.GetFileName(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelUpload 监测客户端取消上传
|
||||||
|
func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHeader) {
|
||||||
|
ginCtx := ctx.Value(GinCtx).(*gin.Context)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// 客户端正常关闭,不执行操作
|
||||||
|
case <-ginCtx.Request.Context().Done():
|
||||||
|
// 客户端取消了上传
|
||||||
|
if fs.AfterUploadCanceled == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, SavePathCtx, path)
|
||||||
|
err := fs.AfterUploadCanceled(ctx, fs)
|
||||||
|
if err != nil {
|
||||||
|
util.Log().Warning("执行 AfterUploadCanceled 钩子出错,%s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* ==========
|
||||||
|
验证器
|
||||||
|
==========
|
||||||
|
*/
|
||||||
|
|
||||||
// 文件/路径名保留字符
|
// 文件/路径名保留字符
|
||||||
var reservedCharacter = []string{"\\", "?", "*", "<", "\"", ":", ">", "/"}
|
var reservedCharacter = []string{"\\", "?", "*", "<", "\"", ":", ">", "/"}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue