Cloudreve/service/explorer/file.go

263 lines
7.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package explorer
import (
"context"
model "github.com/HFO4/cloudreve/models"
"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"
"net/url"
"path"
"strconv"
"time"
)
// SingleFileService 对单文件进行操作的五福path为文件完整路径
type SingleFileService struct {
Path string `uri:"path" binding:"required,min=1,max=65535"`
}
// FileAnonymousGetService 匿名(外链)获取文件服务
type FileAnonymousGetService struct {
ID uint `uri:"id" binding:"required,min=1"`
Name string `uri:"name" binding:"required"`
}
// DownloadService 文件下載服务
type DownloadService struct {
ID string `uri:"id" binding:"required"`
}
// DownloadArchived 下載已打包的多文件
func (service *DownloadService) DownloadArchived(ctx context.Context, c *gin.Context) serializer.Response {
// 创建文件系统
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
// 查找打包的临时文件
zipPath, exist := cache.Get("archive_" + service.ID)
if !exist {
return serializer.Err(404, "归档文件不存在", nil)
}
// 获取文件流
rs, err := fs.GetPhysicalFileContent(ctx, zipPath.(string))
defer rs.Close()
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
if fs.User.Group.OptionsSerialized.OneTimeDownloadEnabled {
// 清理资源,删除临时文件
_ = cache.Deletes([]string{service.ID}, "archive_")
}
c.Header("Content-Disposition", "attachment;")
c.Header("Content-Type", "application/zip")
http.ServeContent(c.Writer, c.Request, "", time.Now(), rs)
return serializer.Response{
Code: 0,
}
}
// Download 签名的匿名文件下载
func (service *FileAnonymousGetService) Download(ctx context.Context, c *gin.Context) serializer.Response {
fs, err := filesystem.NewAnonymousFileSystem()
if err != nil {
return serializer.Err(serializer.CodeGroupNotAllowed, err.Error(), err)
}
// 查找文件
err = fs.SetTargetFileByIDs([]uint{service.ID})
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
// 获取文件流
rs, err := fs.GetDownloadContent(ctx, "")
defer rs.Close()
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
// 发送文件
http.ServeContent(c.Writer, c.Request, service.Name, fs.FileTarget[0].UpdatedAt, rs)
return serializer.Response{
Code: 0,
}
}
// CreateDocPreviewSession 创建DOC文件预览会话返回预览地址
func (service *SingleFileService) CreateDocPreviewSession(ctx context.Context, c *gin.Context) serializer.Response {
// 创建文件系统
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
// 获取文件临时下载地址
downloadURL, err := fs.GetDownloadURL(ctx, service.Path, "doc_preview_timeout")
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
// 生成最终的预览器地址
// TODO 从配置文件中读取
viewerBase, _ := url.Parse("https://view.officeapps.live.com/op/view.aspx")
params := viewerBase.Query()
params.Set("src", downloadURL)
viewerBase.RawQuery = params.Encode()
return serializer.Response{
Code: 0,
Data: viewerBase.String(),
}
}
// CreateDownloadSession 创建下载会话获取下载URL
func (service *SingleFileService) CreateDownloadSession(ctx context.Context, c *gin.Context) serializer.Response {
// 创建文件系统
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
// 获取下载地址
downloadURL, err := fs.GetDownloadURL(ctx, service.Path, "download_timeout")
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
return serializer.Response{
Code: 0,
Data: downloadURL,
}
}
// Download 通过签名URL的文件下载无需登录
func (service *DownloadService) Download(ctx context.Context, c *gin.Context) serializer.Response {
// 创建文件系统
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
// 查找打包的临时文件
file, exist := cache.Get("download_" + service.ID)
if !exist {
return serializer.Err(404, "文件下载会话不存在", nil)
}
fs.FileTarget = []model.File{file.(model.File)}
// 开始处理下载
ctx = context.WithValue(ctx, fsctx.GinCtx, c)
rs, err := fs.GetDownloadContent(ctx, "")
defer rs.Close()
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
// 设置文件名
c.Header("Content-Disposition", "attachment; filename=\""+url.PathEscape(fs.FileTarget[0].Name)+"\"")
if fs.User.Group.OptionsSerialized.OneTimeDownloadEnabled {
// 清理资源,删除临时文件
_ = cache.Deletes([]string{service.ID}, "download_")
}
// 发送文件
http.ServeContent(c.Writer, c.Request, fs.FileTarget[0].Name, fs.FileTarget[0].UpdatedAt, rs)
return serializer.Response{
Code: 0,
}
}
// PreviewContent 预览文件,需要登录会话
func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Context) serializer.Response {
// 创建文件系统
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
// 获取文件流
rs, err := fs.GetDownloadContent(ctx, service.Path)
if err != nil {
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
}
defer rs.Close()
http.ServeContent(c.Writer, c.Request, fs.FileTarget[0].Name, fs.FileTarget[0].UpdatedAt, rs)
return serializer.Response{
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,
}
}