refactor(thumb): thumb logic for slave policy
This commit is contained in:
parent
ae118c337e
commit
7cb5e68b78
15 changed files with 75 additions and 78 deletions
|
@ -48,6 +48,12 @@ func Init(path string, statics fs.FS) {
|
|||
model.Init()
|
||||
},
|
||||
},
|
||||
{
|
||||
"slave",
|
||||
func() {
|
||||
model.InitSlaveDefaults()
|
||||
},
|
||||
},
|
||||
{
|
||||
"both",
|
||||
func() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
"github.com/gofrs/uuid"
|
||||
|
@ -125,3 +126,9 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
|
|||
{Name: "wopi_max_size", Value: "52428800", Type: "wopi"},
|
||||
{Name: "wopi_session_timeout", Value: "36000", Type: "wopi"},
|
||||
}
|
||||
|
||||
func InitSlaveDefaults() {
|
||||
for _, setting := range defaultSettings {
|
||||
cache.Set("setting_"+setting.Name, setting.Value, -1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -464,10 +464,5 @@ func (file *File) GetPosition() string {
|
|||
// `True` does not guarantee the load request will success in next step, but the client
|
||||
// should try to load and fallback to default placeholder in case error returned.
|
||||
func (file *File) ShouldLoadThumb() bool {
|
||||
switch file.GetPolicy().Type {
|
||||
case "local":
|
||||
return file.MetadataSerialized[ThumbStatusMetadataKey] != ThumbStatusNotAvailable
|
||||
default:
|
||||
return file.PicInfo != "" && file.PicInfo != " " && file.PicInfo != "null,null"
|
||||
}
|
||||
return file.MetadataSerialized[ThumbStatusMetadataKey] != ThumbStatusNotAvailable
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||
|
@ -68,18 +67,8 @@ type PolicyOption struct {
|
|||
// Set this to `true` to force the request to use path-style addressing,
|
||||
// i.e., `http://s3.amazonaws.com/BUCKET/KEY `
|
||||
S3ForcePathStyle bool `json:"s3_path_style"`
|
||||
}
|
||||
|
||||
// thumbSuffix 支持缩略图处理的文件扩展名
|
||||
var thumbSuffix = map[string][]string{
|
||||
"local": {},
|
||||
"qiniu": {".psd", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"},
|
||||
"oss": {".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"},
|
||||
"cos": {".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"},
|
||||
"upyun": {".svg", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"},
|
||||
"s3": {},
|
||||
"remote": {},
|
||||
"onedrive": {"*"},
|
||||
// File extensions that support thumbnail generation using native policy API.
|
||||
ThumbExts []string `json:"thumb_exts,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -192,17 +181,6 @@ func (policy *Policy) IsDirectlyPreview() bool {
|
|||
return policy.Type == "local"
|
||||
}
|
||||
|
||||
// IsThumbExist 给定文件名,返回此存储策略下是否可能存在缩略图
|
||||
func (policy *Policy) IsThumbExist(name string) bool {
|
||||
if list, ok := thumbSuffix[policy.Type]; ok {
|
||||
if len(list) == 1 && list[0] == "*" {
|
||||
return true
|
||||
}
|
||||
return util.ContainsString(list, strings.ToLower(filepath.Ext(name)))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsTransitUpload 返回此策略上传给定size文件时是否需要服务端中转
|
||||
func (policy *Policy) IsTransitUpload(size uint64) bool {
|
||||
return policy.Type == "local"
|
||||
|
|
|
@ -11,7 +11,8 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrorThumbNotExist = fmt.Errorf("thumb not exist")
|
||||
ErrorThumbNotExist = fmt.Errorf("thumb not exist")
|
||||
ErrorThumbNotSupported = fmt.Errorf("thumb not supported")
|
||||
)
|
||||
|
||||
// Handler 存储策略适配器
|
||||
|
@ -28,6 +29,9 @@ type Handler interface {
|
|||
|
||||
// 获取缩略图,可直接在ContentResponse中返回文件数据流,也可指
|
||||
// 定为重定向
|
||||
// 如果缩略图不存在, 且需要 Cloudreve 代理生成并上传,应返回 ErrorThumbNotExist,生
|
||||
// 成的缩略图文件存储规则与本机策略一致。
|
||||
// 如果不支持此文件的缩略图,并且不希望后续继续请求此缩略图,应返回 ErrorThumbNotSupported
|
||||
Thumb(ctx context.Context, file *model.File) (*response.ContentResponse, error)
|
||||
|
||||
// 获取外链/下载地址,
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
|
||||
|
@ -196,13 +197,18 @@ func (handler Driver) Delete(ctx context.Context, files []string) ([]string, err
|
|||
|
||||
// Thumb 获取文件缩略图
|
||||
func (handler Driver) Thumb(ctx context.Context, file *model.File) (*response.ContentResponse, error) {
|
||||
if file.MetadataSerialized[model.ThumbStatusMetadataKey] == model.ThumbStatusNotExist {
|
||||
// Quick check thumb existence on master.
|
||||
if conf.SystemConfig.Mode == "master" && file.MetadataSerialized[model.ThumbStatusMetadataKey] == model.ThumbStatusNotExist {
|
||||
// Tell invoker to generate a thumb
|
||||
return nil, driver.ErrorThumbNotExist
|
||||
}
|
||||
|
||||
thumbFile, err := handler.Get(ctx, file.SourceName+model.GetSettingByNameWithDefault("thumb_file_suffix", "._thumb"))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = fmt.Errorf("thumb not exist: %w (%w)", err, driver.ErrorThumbNotExist)
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,15 +8,18 @@ import (
|
|||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/auth"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem/response"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/request"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
)
|
||||
|
||||
// Driver 远程存储策略适配器
|
||||
|
@ -205,8 +208,18 @@ func (handler *Driver) Delete(ctx context.Context, files []string) ([]string, er
|
|||
|
||||
// Thumb 获取文件缩略图
|
||||
func (handler *Driver) Thumb(ctx context.Context, file *model.File) (*response.ContentResponse, error) {
|
||||
// quick check by extensions
|
||||
supported := []string{"png", "jpg", "jpeg", "gif"}
|
||||
if len(handler.Policy.OptionsSerialized.ThumbExts) > 0 {
|
||||
supported = handler.Policy.OptionsSerialized.ThumbExts
|
||||
}
|
||||
|
||||
if !util.IsInExtensionList(supported, file.Name) {
|
||||
return nil, driver.ErrorThumbNotSupported
|
||||
}
|
||||
|
||||
sourcePath := base64.RawURLEncoding.EncodeToString([]byte(file.SourceName))
|
||||
thumbURL := handler.getAPIUrl("thumb") + "/" + sourcePath
|
||||
thumbURL := fmt.Sprintf("%s/%s/%s", handler.getAPIUrl("thumb"), sourcePath, filepath.Ext(file.Name))
|
||||
ttl := model.GetIntSetting("preview_timeout", 60)
|
||||
signedThumbURL, err := auth.SignURI(handler.AuthInstance, thumbURL, int64(ttl))
|
||||
if err != nil {
|
||||
|
|
|
@ -64,10 +64,6 @@ func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder, file fs
|
|||
UploadSessionID: uploadInfo.UploadSessionID,
|
||||
}
|
||||
|
||||
if fs.Policy.IsThumbExist(uploadInfo.FileName) {
|
||||
newFile.PicInfo = "1,1"
|
||||
}
|
||||
|
||||
err = newFile.Create()
|
||||
|
||||
if err != nil {
|
||||
|
@ -97,9 +93,10 @@ func (fs *FileSystem) GetPhysicalFileContent(ctx context.Context, path string) (
|
|||
}
|
||||
|
||||
// Preview 预览文件
|
||||
// path - 文件虚拟路径
|
||||
// isText - 是否为文本文件,文本文件会忽略重定向,直接由
|
||||
// 服务端拉取中转给用户,故会对文件大小进行限制
|
||||
//
|
||||
// path - 文件虚拟路径
|
||||
// isText - 是否为文本文件,文本文件会忽略重定向,直接由
|
||||
// 服务端拉取中转给用户,故会对文件大小进行限制
|
||||
func (fs *FileSystem) Preview(ctx context.Context, id uint, isText bool) (*response.ContentResponse, error) {
|
||||
err := fs.resetFileIDIfNotExist(ctx, id)
|
||||
if err != nil {
|
||||
|
|
|
@ -177,23 +177,12 @@ func GenericAfterUpdate(ctx context.Context, fs *FileSystem, newFile fsctx.FileH
|
|||
// SlaveAfterUpload Slave模式下上传完成钩子
|
||||
func SlaveAfterUpload(session *serializer.UploadSession) Hook {
|
||||
return func(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
|
||||
fileInfo := fileHeader.Info()
|
||||
|
||||
// 构造一个model.File,用于生成缩略图
|
||||
file := model.File{
|
||||
Name: fileInfo.FileName,
|
||||
SourceName: fileInfo.SavePath,
|
||||
}
|
||||
|
||||
if session.Callback == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 发送回调请求
|
||||
callbackBody := serializer.UploadCallback{
|
||||
PicInfo: file.PicInfo,
|
||||
}
|
||||
|
||||
callbackBody := serializer.UploadCallback{}
|
||||
return cluster.RemoteCallback(session.Callback, callbackBody)
|
||||
}
|
||||
}
|
||||
|
@ -268,10 +257,6 @@ func HookPopPlaceholderToFile(picInfo string) Hook {
|
|||
return func(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
|
||||
fileInfo := fileHeader.Info()
|
||||
fileModel := fileInfo.Model.(*model.File)
|
||||
if picInfo == "" && fs.Policy.IsThumbExist(fileInfo.FileName) {
|
||||
picInfo = "1,1"
|
||||
}
|
||||
|
||||
return fileModel.PopChunkToFile(fileInfo.LastModified, picInfo)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@ func (fs *FileSystem) GetThumb(ctx context.Context, id uint) (*response.ContentR
|
|||
// Regenerate thumb if the thumb is not initialized yet
|
||||
fs.GenerateThumbnail(ctx, &fs.FileTarget[0])
|
||||
res, err = fs.Handler.Thumb(ctx, &fs.FileTarget[0])
|
||||
} else if errors.Is(err, driver.ErrorThumbNotSupported) {
|
||||
// Policy handler explicitly indicates thumb not available
|
||||
_ = updateThumbStatus(&fs.FileTarget[0], model.ThumbStatusNotAvailable)
|
||||
}
|
||||
|
||||
if err == nil && conf.SystemConfig.Mode == "master" {
|
||||
|
@ -157,6 +160,10 @@ func updateThumbStatus(file *model.File, status string) error {
|
|||
model.ThumbStatusMetadataKey: status,
|
||||
})
|
||||
} else {
|
||||
if file.MetadataSerialized == nil {
|
||||
file.MetadataSerialized = map[string]string{}
|
||||
}
|
||||
|
||||
file.MetadataSerialized[model.ThumbStatusMetadataKey] = status
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package filesystem
|
|||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
|
@ -63,20 +62,5 @@ func (fs *FileSystem) ValidateExtension(ctx context.Context, fileName string) bo
|
|||
return true
|
||||
}
|
||||
|
||||
return IsInExtensionList(fs.Policy.OptionsSerialized.FileType, fileName)
|
||||
}
|
||||
|
||||
// IsInExtensionList 返回文件的扩展名是否在给定的列表范围内
|
||||
func IsInExtensionList(extList []string, fileName string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
// 无扩展名时
|
||||
if len(ext) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if util.ContainsString(extList, ext[1:]) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return util.IsInExtensionList(fs.Policy.OptionsSerialized.FileType, fileName)
|
||||
}
|
||||
|
|
|
@ -39,9 +39,7 @@ func NewThumbFromFile(file io.Reader, name string) (*Thumb, error) {
|
|||
var err error
|
||||
var img image.Image
|
||||
switch ext[1:] {
|
||||
case "jpg":
|
||||
img, err = jpeg.Decode(file)
|
||||
case "jpeg":
|
||||
case "jpg", "jpeg":
|
||||
img, err = jpeg.Decode(file)
|
||||
case "gif":
|
||||
img, err = gif.Decode(file)
|
||||
|
|
|
@ -2,6 +2,7 @@ package util
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -32,6 +33,21 @@ func ContainsUint(s []uint, e uint) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// IsInExtensionList 返回文件的扩展名是否在给定的列表范围内
|
||||
func IsInExtensionList(extList []string, fileName string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
// 无扩展名时
|
||||
if len(ext) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if ContainsString(extList, ext[1:]) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsString 返回list中是否包含
|
||||
func ContainsString(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
|
|
|
@ -64,7 +64,7 @@ func InitSlaveRouter() *gin.Engine {
|
|||
// 预览 / 外链
|
||||
v3.GET("source/:speed/:path/:name", controllers.SlavePreview)
|
||||
// 缩略图
|
||||
v3.GET("thumb/:path", controllers.SlaveThumb)
|
||||
v3.GET("thumb/:path/:ext", controllers.SlaveThumb)
|
||||
// 删除文件
|
||||
v3.POST("delete", controllers.SlaveDelete)
|
||||
// 列出文件
|
||||
|
|
|
@ -32,6 +32,7 @@ type SlaveDownloadService struct {
|
|||
// SlaveFileService 从机单文件文件相关服务
|
||||
type SlaveFileService struct {
|
||||
PathEncoded string `uri:"path" binding:"required"`
|
||||
Ext string `uri:"ext"`
|
||||
}
|
||||
|
||||
// SlaveFilesService 从机多文件相关服务
|
||||
|
@ -132,7 +133,7 @@ func (service *SlaveFileService) Thumb(ctx context.Context, c *gin.Context) seri
|
|||
if err != nil {
|
||||
return serializer.Err(serializer.CodeFileNotFound, "", err)
|
||||
}
|
||||
fs.FileTarget = []model.File{{SourceName: string(fileSource), PicInfo: "1,1"}}
|
||||
fs.FileTarget = []model.File{{SourceName: string(fileSource), Name: fmt.Sprintf("%s.%s", fileSource, service.Ext), PicInfo: "1,1"}}
|
||||
|
||||
// 获取缩略图
|
||||
resp, err := fs.GetThumb(ctx, 0)
|
||||
|
|
Loading…
Add table
Reference in a new issue