Feat: update file content

This commit is contained in:
HFO4 2019-12-15 14:01:37 +08:00
parent 2c75c73886
commit 8bf2966d46
6 changed files with 175 additions and 10 deletions

View file

@ -161,3 +161,8 @@ func (file *File) Rename(new string) error {
func (file *File) UpdatePicInfo(value string) error { func (file *File) UpdatePicInfo(value string) error {
return DB.Model(&file).Update("pic_info", value).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
}

View file

@ -3,8 +3,12 @@ package filesystem
import ( import (
"context" "context"
"errors" "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/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/pkg/util"
"io/ioutil"
"strings"
) )
// Hook 钩子函数 // 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 验证并扣除用户容量,包含数据库操作 // HookValidateCapacity 验证并扣除用户容量,包含数据库操作
func HookValidateCapacity(ctx context.Context, fs *FileSystem) error { func HookValidateCapacity(ctx context.Context, fs *FileSystem) error {
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader) file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
@ -81,10 +96,27 @@ func HookValidateCapacity(ctx context.Context, fs *FileSystem) error {
return nil 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 删除已保存的临时文件 // HookDeleteTempFile 删除已保存的临时文件
func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error { func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error {
filePath := ctx.Value(fsctx.SavePathCtx).(string) filePath := ctx.Value(fsctx.SavePathCtx).(string)
// 删除临时文件 // 删除临时文件
// TODO 其他策略。Exists
if util.Exists(filePath) { if util.Exists(filePath) {
_, err := fs.Handler.Delete(ctx, []string{filePath}) _, err := fs.Handler.Delete(ctx, []string{filePath})
if err != nil { if err != nil {
@ -95,6 +127,22 @@ func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error {
return nil 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 归还用户容量 // HookGiveBackCapacity 归还用户容量
func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error { func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error {
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader) file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
@ -106,6 +154,34 @@ func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error {
return nil 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 文件上传完成后,包含数据库操作 // GenericAfterUpload 文件上传完成后,包含数据库操作
func GenericAfterUpload(ctx context.Context, fs *FileSystem) error { func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
// 文件存放的虚拟路径 // 文件存放的虚拟路径

View file

@ -2,6 +2,7 @@ package filesystem
import ( import (
"context" "context"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/util" "github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -23,8 +24,14 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
return err 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) go fs.CancelUpload(ctx, savePath, file)
@ -50,7 +57,7 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
return err 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 return nil
} }

View file

@ -143,7 +143,7 @@ func Preview(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.FileDownloadCreateService var service explorer.SingleFileService
if err := c.ShouldBindUri(&service); err == nil { if err := c.ShouldBindUri(&service); err == nil {
res := service.PreviewContent(ctx, c) res := service.PreviewContent(ctx, c)
// 是否需要重定向 // 是否需要重定向
@ -164,7 +164,7 @@ func CreateDownloadSession(c *gin.Context) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
var service explorer.FileDownloadCreateService var service explorer.SingleFileService
if err := c.ShouldBindUri(&service); err == nil { if err := c.ShouldBindUri(&service); err == nil {
res := service.CreateDownloadSession(ctx, c) res := service.CreateDownloadSession(ctx, c)
c.JSON(200, res) 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 本地策略流式上传 // FileUploadStream 本地策略流式上传
func FileUploadStream(c *gin.Context) { func FileUploadStream(c *gin.Context) {
// 创建上下文 // 创建上下文
@ -251,6 +266,5 @@ func FileUploadStream(c *gin.Context) {
c.JSON(200, serializer.Response{ c.JSON(200, serializer.Response{
Code: 0, Code: 0,
Msg: "Pong",
}) })
} }

View file

@ -104,6 +104,8 @@ func InitRouter() *gin.Engine {
{ {
// 文件上传 // 文件上传
file.POST("upload", controllers.FileUploadStream) file.POST("upload", controllers.FileUploadStream)
// 更新文件
file.PUT("update/*path", controllers.PutContent)
// 创建文件下载会话 // 创建文件下载会话
file.PUT("download/*path", controllers.CreateDownloadSession) file.PUT("download/*path", controllers.CreateDownloadSession)
// 预览文件 // 预览文件

View file

@ -6,14 +6,17 @@ import (
"github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/cache"
"github.com/HFO4/cloudreve/pkg/filesystem" "github.com/HFO4/cloudreve/pkg/filesystem"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx" "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/serializer"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"path"
"strconv"
"time" "time"
) )
// FileDownloadCreateService 文件下载会话创建服务path为文件完整路径 // SingleFileService 对单文件进行操作的五福path为文件完整路径
type FileDownloadCreateService struct { type SingleFileService struct {
Path string `uri:"path" binding:"required,min=1,max=65535"` 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 // 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) fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil { if err != nil {
@ -152,7 +155,7 @@ func (service *DownloadService) Download(ctx context.Context, c *gin.Context) se
} }
// PreviewContent 预览文件,需要登录会话 // 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) fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil { if err != nil {
@ -172,3 +175,61 @@ func (service *FileDownloadCreateService) PreviewContent(ctx context.Context, c
Code: 0, 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,
}
}