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 {
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
// 文件存放的虚拟路径
|
// 文件存放的虚拟路径
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
// 预览文件
|
// 预览文件
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue