Feat: update file content
This commit is contained in:
parent
2c75c73886
commit
8bf2966d46
6 changed files with 175 additions and 10 deletions
|
@ -161,3 +161,8 @@ func (file *File) Rename(new string) error {
|
|||
func (file *File) UpdatePicInfo(value string) error {
|
||||
return DB.Model(&file).Update("pic_info", value).Error
|
||||
}
|
||||
|
||||
// UpdatePicInfo 更新文件的图像信息
|
||||
func (file *File) UpdateSize(value uint64) error {
|
||||
return DB.Model(&file).Update("size", value).Error
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@ package filesystem
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/conf"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Hook 钩子函数
|
||||
|
@ -71,6 +75,17 @@ func HookValidateFile(ctx context.Context, fs *FileSystem) error {
|
|||
|
||||
}
|
||||
|
||||
// HookResetPolicy 重设存储策略为已有文件
|
||||
func HookResetPolicy(ctx context.Context, fs *FileSystem) error {
|
||||
originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
||||
if !ok {
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
|
||||
fs.Policy = originFile.GetPolicy()
|
||||
return fs.dispatchHandler()
|
||||
}
|
||||
|
||||
// HookValidateCapacity 验证并扣除用户容量,包含数据库操作
|
||||
func HookValidateCapacity(ctx context.Context, fs *FileSystem) error {
|
||||
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
|
||||
|
@ -81,10 +96,27 @@ func HookValidateCapacity(ctx context.Context, fs *FileSystem) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// HookChangeCapacity 根据原有文件和新文件的大小更新用户容量
|
||||
func HookChangeCapacity(ctx context.Context, fs *FileSystem) error {
|
||||
newFile := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
|
||||
originFile := ctx.Value(fsctx.FileModelCtx).(model.File)
|
||||
|
||||
if newFile.GetSize() > originFile.Size {
|
||||
if !fs.ValidateCapacity(ctx, newFile.GetSize()-originFile.Size) {
|
||||
return ErrInsufficientCapacity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fs.User.DeductionStorage(originFile.Size - newFile.GetSize())
|
||||
return nil
|
||||
}
|
||||
|
||||
// HookDeleteTempFile 删除已保存的临时文件
|
||||
func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error {
|
||||
filePath := ctx.Value(fsctx.SavePathCtx).(string)
|
||||
// 删除临时文件
|
||||
// TODO 其他策略。Exists?
|
||||
if util.Exists(filePath) {
|
||||
_, err := fs.Handler.Delete(ctx, []string{filePath})
|
||||
if err != nil {
|
||||
|
@ -95,6 +127,22 @@ func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// HookCleanFileContent 清空文件内容
|
||||
func HookCleanFileContent(ctx context.Context, fs *FileSystem) error {
|
||||
filePath := ctx.Value(fsctx.SavePathCtx).(string)
|
||||
// 清空内容
|
||||
return fs.Handler.Put(ctx, ioutil.NopCloser(strings.NewReader("")), filePath, 0)
|
||||
}
|
||||
|
||||
// HookClearFileSize 将原始文件的尺寸设为0
|
||||
func HookClearFileSize(ctx context.Context, fs *FileSystem) error {
|
||||
originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
||||
if !ok {
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
return originFile.UpdateSize(0)
|
||||
}
|
||||
|
||||
// HookGiveBackCapacity 归还用户容量
|
||||
func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error {
|
||||
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
|
||||
|
@ -106,6 +154,34 @@ func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GenericAfterUpdate 文件内容更新后
|
||||
func GenericAfterUpdate(ctx context.Context, fs *FileSystem) error {
|
||||
// 更新文件尺寸
|
||||
originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
||||
if !ok {
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
newFile, ok := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
|
||||
if !ok {
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
err := originFile.UpdateSize(newFile.GetSize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 尝试清空原有缩略图
|
||||
go func() {
|
||||
if originFile.PicInfo != "" {
|
||||
_, _ = fs.Handler.Delete(ctx, []string{originFile.SourceName + conf.ThumbConfig.FileSuffix})
|
||||
fs.GenerateThumbnail(ctx, &originFile)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenericAfterUpload 文件上传完成后,包含数据库操作
|
||||
func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
|
||||
// 文件存放的虚拟路径
|
||||
|
|
|
@ -2,6 +2,7 @@ package filesystem
|
|||
|
||||
import (
|
||||
"context"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -23,8 +24,14 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
// 生成文件名和路径
|
||||
savePath := fs.GenerateSavePath(ctx, file)
|
||||
// 生成文件名和路径, 如果是更新操作就从原始文件获取
|
||||
var savePath string
|
||||
originFile, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
|
||||
if ok {
|
||||
savePath = originFile.SourceName
|
||||
} else {
|
||||
savePath = fs.GenerateSavePath(ctx, file)
|
||||
}
|
||||
|
||||
// 处理客户端未完成上传时,关闭连接
|
||||
go fs.CancelUpload(ctx, savePath, file)
|
||||
|
@ -50,7 +57,7 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
util.Log().Info("新文件上传:%s , 大小:%d, 上传者:%s", file.GetFileName(), file.GetSize(), fs.User.Nick)
|
||||
util.Log().Info("新文件PUT:%s , 大小:%d, 上传者:%s", file.GetFileName(), file.GetSize(), fs.User.Nick)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ func Preview(c *gin.Context) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var service explorer.FileDownloadCreateService
|
||||
var service explorer.SingleFileService
|
||||
if err := c.ShouldBindUri(&service); err == nil {
|
||||
res := service.PreviewContent(ctx, c)
|
||||
// 是否需要重定向
|
||||
|
@ -164,7 +164,7 @@ func CreateDownloadSession(c *gin.Context) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var service explorer.FileDownloadCreateService
|
||||
var service explorer.SingleFileService
|
||||
if err := c.ShouldBindUri(&service); err == nil {
|
||||
res := service.CreateDownloadSession(ctx, c)
|
||||
c.JSON(200, res)
|
||||
|
@ -190,6 +190,21 @@ func Download(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
// PutContent 更新文件内容
|
||||
func PutContent(c *gin.Context) {
|
||||
// 创建上下文
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var service explorer.SingleFileService
|
||||
if err := c.ShouldBindUri(&service); err == nil {
|
||||
res := service.PutContent(ctx, c)
|
||||
c.JSON(200, res)
|
||||
} else {
|
||||
c.JSON(200, ErrorResponse(err))
|
||||
}
|
||||
}
|
||||
|
||||
// FileUploadStream 本地策略流式上传
|
||||
func FileUploadStream(c *gin.Context) {
|
||||
// 创建上下文
|
||||
|
@ -251,6 +266,5 @@ func FileUploadStream(c *gin.Context) {
|
|||
|
||||
c.JSON(200, serializer.Response{
|
||||
Code: 0,
|
||||
Msg: "Pong",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -104,6 +104,8 @@ func InitRouter() *gin.Engine {
|
|||
{
|
||||
// 文件上传
|
||||
file.POST("upload", controllers.FileUploadStream)
|
||||
// 更新文件
|
||||
file.PUT("update/*path", controllers.PutContent)
|
||||
// 创建文件下载会话
|
||||
file.PUT("download/*path", controllers.CreateDownloadSession)
|
||||
// 预览文件
|
||||
|
|
|
@ -6,14 +6,17 @@ import (
|
|||
"github.com/HFO4/cloudreve/pkg/cache"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FileDownloadCreateService 文件下载会话创建服务,path为文件完整路径
|
||||
type FileDownloadCreateService struct {
|
||||
// SingleFileService 对单文件进行操作的五福,path为文件完整路径
|
||||
type SingleFileService struct {
|
||||
Path string `uri:"path" binding:"required,min=1,max=65535"`
|
||||
}
|
||||
|
||||
|
@ -93,7 +96,7 @@ func (service *FileAnonymousGetService) Download(ctx context.Context, c *gin.Con
|
|||
}
|
||||
|
||||
// CreateDownloadSession 创建下载会话,获取下载URL
|
||||
func (service *FileDownloadCreateService) CreateDownloadSession(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
func (service *SingleFileService) CreateDownloadSession(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
|
@ -152,7 +155,7 @@ func (service *DownloadService) Download(ctx context.Context, c *gin.Context) se
|
|||
}
|
||||
|
||||
// PreviewContent 预览文件,需要登录会话
|
||||
func (service *FileDownloadCreateService) PreviewContent(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
|
@ -172,3 +175,61 @@ func (service *FileDownloadCreateService) PreviewContent(ctx context.Context, c
|
|||
Code: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// PutContent 更新文件内容
|
||||
func (service *SingleFileService) PutContent(ctx context.Context, c *gin.Context) serializer.Response {
|
||||
// 创建上下文
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// 取得文件大小
|
||||
fileSize, err := strconv.ParseUint(c.Request.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
|
||||
return serializer.ParamErr("无法解析文件尺寸", err)
|
||||
}
|
||||
|
||||
fileData := local.FileStream{
|
||||
MIMEType: c.Request.Header.Get("Content-Type"),
|
||||
File: c.Request.Body,
|
||||
Size: fileSize,
|
||||
Name: path.Base(service.Path),
|
||||
VirtualPath: path.Dir(service.Path),
|
||||
}
|
||||
|
||||
// 创建文件系统
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
|
||||
}
|
||||
|
||||
// 取得现有文件
|
||||
exist, originFile := fs.IsFileExist(service.Path)
|
||||
if !exist {
|
||||
return serializer.Err(404, "文件不存在", nil)
|
||||
}
|
||||
|
||||
// 给文件系统分配钩子
|
||||
fs.Use("BeforeUpload", filesystem.HookValidateFile)
|
||||
fs.Use("BeforeUpload", filesystem.HookResetPolicy)
|
||||
fs.Use("BeforeUpload", filesystem.HookChangeCapacity)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookGiveBackCapacity)
|
||||
fs.Use("AfterUpload", filesystem.GenericAfterUpdate)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookCleanFileContent)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookClearFileSize)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity)
|
||||
|
||||
// 执行上传
|
||||
uploadCtx := context.WithValue(ctx, fsctx.GinCtx, c)
|
||||
uploadCtx = context.WithValue(uploadCtx, fsctx.FileModelCtx, *originFile)
|
||||
err = fs.Upload(uploadCtx, fileData)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeUploadFailed, err.Error(), err)
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
Code: 0,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue