From 7cb5e68b78b9a478a9a930dfb014519b0d53fee4 Mon Sep 17 00:00:00 2001 From: Aaron Liu <912394456@qq.com> Date: Fri, 7 Apr 2023 19:25:29 +0800 Subject: [PATCH] refactor(thumb): thumb logic for slave policy --- bootstrap/init.go | 6 ++++++ models/defaults.go | 7 +++++++ models/file.go | 7 +------ models/policy.go | 26 ++----------------------- pkg/filesystem/driver/handler.go | 6 +++++- pkg/filesystem/driver/local/handler.go | 8 +++++++- pkg/filesystem/driver/remote/handler.go | 15 +++++++++++++- pkg/filesystem/file.go | 11 ++++------- pkg/filesystem/hooks.go | 17 +--------------- pkg/filesystem/image.go | 7 +++++++ pkg/filesystem/validator.go | 18 +---------------- pkg/thumb/builtin.go | 4 +--- pkg/util/common.go | 16 +++++++++++++++ routers/router.go | 2 +- service/explorer/slave.go | 3 ++- 15 files changed, 75 insertions(+), 78 deletions(-) diff --git a/bootstrap/init.go b/bootstrap/init.go index f959d5e..a9e7c21 100644 --- a/bootstrap/init.go +++ b/bootstrap/init.go @@ -48,6 +48,12 @@ func Init(path string, statics fs.FS) { model.Init() }, }, + { + "slave", + func() { + model.InitSlaveDefaults() + }, + }, { "both", func() { diff --git a/models/defaults.go b/models/defaults.go index dbaecb4..c40f346 100644 --- a/models/defaults.go +++ b/models/defaults.go @@ -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) + } +} diff --git a/models/file.go b/models/file.go index 458cf2d..349504f 100644 --- a/models/file.go +++ b/models/file.go @@ -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 } diff --git a/models/policy.go b/models/policy.go index f1e0202..20e4631 100644 --- a/models/policy.go +++ b/models/policy.go @@ -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" diff --git a/pkg/filesystem/driver/handler.go b/pkg/filesystem/driver/handler.go index 8b2a08d..608fe81 100644 --- a/pkg/filesystem/driver/handler.go +++ b/pkg/filesystem/driver/handler.go @@ -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) // 获取外链/下载地址, diff --git a/pkg/filesystem/driver/local/handler.go b/pkg/filesystem/driver/local/handler.go index 50af796..bceff31 100644 --- a/pkg/filesystem/driver/local/handler.go +++ b/pkg/filesystem/driver/local/handler.go @@ -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 } diff --git a/pkg/filesystem/driver/remote/handler.go b/pkg/filesystem/driver/remote/handler.go index 06c9efe..663f137 100644 --- a/pkg/filesystem/driver/remote/handler.go +++ b/pkg/filesystem/driver/remote/handler.go @@ -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 { diff --git a/pkg/filesystem/file.go b/pkg/filesystem/file.go index 45f721c..d22acb8 100644 --- a/pkg/filesystem/file.go +++ b/pkg/filesystem/file.go @@ -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 { diff --git a/pkg/filesystem/hooks.go b/pkg/filesystem/hooks.go index bd34a64..2e887da 100644 --- a/pkg/filesystem/hooks.go +++ b/pkg/filesystem/hooks.go @@ -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) } } diff --git a/pkg/filesystem/image.go b/pkg/filesystem/image.go index 396e963..75eb7fa 100644 --- a/pkg/filesystem/image.go +++ b/pkg/filesystem/image.go @@ -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 } diff --git a/pkg/filesystem/validator.go b/pkg/filesystem/validator.go index c7504f4..1992547 100644 --- a/pkg/filesystem/validator.go +++ b/pkg/filesystem/validator.go @@ -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) } diff --git a/pkg/thumb/builtin.go b/pkg/thumb/builtin.go index e7600cb..f7734e0 100644 --- a/pkg/thumb/builtin.go +++ b/pkg/thumb/builtin.go @@ -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) diff --git a/pkg/util/common.go b/pkg/util/common.go index 4fe5151..fe1fa91 100644 --- a/pkg/util/common.go +++ b/pkg/util/common.go @@ -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 { diff --git a/routers/router.go b/routers/router.go index d28a93b..8d872c7 100644 --- a/routers/router.go +++ b/routers/router.go @@ -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) // 列出文件 diff --git a/service/explorer/slave.go b/service/explorer/slave.go index afb61af..eee840c 100644 --- a/service/explorer/slave.go +++ b/service/explorer/slave.go @@ -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)