mirror of
https://github.com/halejohn/Cloudreve.git
synced 2026-01-26 09:34:57 +08:00
feat(thumb): generate and return sidecar thumb
This commit is contained in:
12
pkg/cache/driver.go
vendored
12
pkg/cache/driver.go
vendored
@@ -10,7 +10,7 @@ import (
|
||||
var Store Driver = NewMemoStore()
|
||||
|
||||
// Init 初始化缓存
|
||||
func Init(isSlave bool) {
|
||||
func Init() {
|
||||
if conf.RedisConfig.Server != "" && gin.Mode() != gin.TestMode {
|
||||
Store = NewRedisStore(
|
||||
10,
|
||||
@@ -20,12 +20,12 @@ func Init(isSlave bool) {
|
||||
conf.RedisConfig.DB,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if isSlave {
|
||||
err := Store.Sets(conf.OptionOverwrite, "setting_")
|
||||
if err != nil {
|
||||
util.Log().Warning("Failed to overwrite database setting: %s", err)
|
||||
}
|
||||
func InitSlaveOverwrites() {
|
||||
err := Store.Sets(conf.OptionOverwrite, "setting_")
|
||||
if err != nil {
|
||||
util.Log().Warning("Failed to overwrite database setting: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ func (handler Driver) Thumb(ctx context.Context, file *model.File) (*response.Co
|
||||
return nil, driver.ErrorThumbNotExist
|
||||
}
|
||||
|
||||
thumbFile, err := handler.Get(ctx, file.SourceName+model.GetSettingByNameWithDefault("thumb_file_suffix", "._thumb"))
|
||||
thumbFile, err := handler.Get(ctx, file.ThumbFile())
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = fmt.Errorf("thumb not exist: %w (%w)", err, driver.ErrorThumbNotExist)
|
||||
|
||||
@@ -208,7 +208,7 @@ 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
|
||||
// quick check by extension name
|
||||
supported := []string{"png", "jpg", "jpeg", "gif"}
|
||||
if len(handler.Policy.OptionsSerialized.ThumbExts) > 0 {
|
||||
supported = handler.Policy.OptionsSerialized.ThumbExts
|
||||
|
||||
@@ -3,7 +3,7 @@ package filesystem
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"runtime"
|
||||
@@ -32,17 +32,42 @@ func (fs *FileSystem) GetThumb(ctx context.Context, id uint) (*response.ContentR
|
||||
}, ErrObjectNotExist
|
||||
}
|
||||
|
||||
file := fs.FileTarget[0]
|
||||
w, h := fs.GenerateThumbnailSize(0, 0)
|
||||
ctx = context.WithValue(ctx, fsctx.ThumbSizeCtx, [2]uint{w, h})
|
||||
ctx = context.WithValue(ctx, fsctx.FileModelCtx, fs.FileTarget[0])
|
||||
res, err := fs.Handler.Thumb(ctx, &fs.FileTarget[0])
|
||||
ctx = context.WithValue(ctx, fsctx.FileModelCtx, file)
|
||||
res, err := fs.Handler.Thumb(ctx, &file)
|
||||
if errors.Is(err, driver.ErrorThumbNotExist) {
|
||||
// Regenerate thumb if the thumb is not initialized yet
|
||||
fs.GenerateThumbnail(ctx, &fs.FileTarget[0])
|
||||
res, err = fs.Handler.Thumb(ctx, &fs.FileTarget[0])
|
||||
fs.GenerateThumbnail(ctx, &file)
|
||||
res, err = fs.Handler.Thumb(ctx, &file)
|
||||
} else if errors.Is(err, driver.ErrorThumbNotSupported) {
|
||||
// Policy handler explicitly indicates thumb not available
|
||||
_ = updateThumbStatus(&fs.FileTarget[0], model.ThumbStatusNotAvailable)
|
||||
// Policy handler explicitly indicates thumb not available, check if proxy is enabled
|
||||
if fs.Policy.CouldProxyThumb() {
|
||||
// if thumb id marked as existed, redirect to "sidecar" thumb file.
|
||||
if file.MetadataSerialized != nil &&
|
||||
file.MetadataSerialized[model.ThumbStatusMetadataKey] == model.ThumbStatusExist {
|
||||
// redirect to sidecar file
|
||||
res = &response.ContentResponse{
|
||||
Redirect: true,
|
||||
}
|
||||
res.URL, err = fs.Handler.Source(
|
||||
ctx,
|
||||
file.ThumbFile(),
|
||||
*model.GetSiteURL(),
|
||||
int64(model.GetIntSetting("preview_timeout", 60)),
|
||||
false,
|
||||
0,
|
||||
)
|
||||
} else {
|
||||
// if not exist, generate and upload the sidecar thumb.
|
||||
fs.GenerateThumbnail(ctx, &file)
|
||||
res, err = fs.Handler.Thumb(ctx, &file)
|
||||
}
|
||||
} else {
|
||||
// thumb not supported and proxy is disabled, mark as not available
|
||||
_ = updateThumbStatus(&file, model.ThumbStatusNotAvailable)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && conf.SystemConfig.Mode == "master" {
|
||||
@@ -100,20 +125,7 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
|
||||
getThumbWorker().addWorker()
|
||||
defer getThumbWorker().releaseWorker()
|
||||
|
||||
r, w := io.Pipe()
|
||||
defer w.Close()
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func(errChan chan error) {
|
||||
errChan <- fs.Handler.Put(newCtx, &fsctx.FileStream{
|
||||
Mode: fsctx.Overwrite,
|
||||
File: io.NopCloser(r),
|
||||
Seeker: nil,
|
||||
SavePath: file.SourceName + model.GetSettingByNameWithDefault("thumb_file_suffix", "._thumb"),
|
||||
})
|
||||
}(errChan)
|
||||
|
||||
if err = thumb.Generators.Generate(source, w, file.Name, model.GetSettingByNames(
|
||||
thumbPath, err := thumb.Generators.Generate(source, file.Name, model.GetSettingByNames(
|
||||
"thumb_width",
|
||||
"thumb_height",
|
||||
"thumb_builtin_enabled",
|
||||
@@ -121,16 +133,33 @@ func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
|
||||
"thumb_ffmpeg_enabled",
|
||||
"thumb_vips_path",
|
||||
"thumb_ffmpeg_path",
|
||||
)); err != nil {
|
||||
))
|
||||
if err != nil {
|
||||
util.Log().Warning("Failed to generate thumb for %s: %s", file.Name, err)
|
||||
_ = updateThumbStatus(file, model.ThumbStatusNotAvailable)
|
||||
w.Close()
|
||||
<-errChan
|
||||
return
|
||||
}
|
||||
|
||||
w.Close()
|
||||
if err = <-errChan; err != nil {
|
||||
thumbFile, err := os.Open(thumbPath)
|
||||
if err != nil {
|
||||
util.Log().Warning("Failed to open temp thumb %q: %s", thumbFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
defer thumbFile.Close()
|
||||
fileInfo, err := thumbFile.Stat()
|
||||
if err != nil {
|
||||
util.Log().Warning("Failed to stat temp thumb %q: %s", thumbFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = fs.Handler.Put(newCtx, &fsctx.FileStream{
|
||||
Mode: fsctx.Overwrite,
|
||||
File: thumbFile,
|
||||
Seeker: thumbFile,
|
||||
Size: uint64(fileInfo.Size()),
|
||||
SavePath: file.SourceName + model.GetSettingByNameWithDefault("thumb_file_suffix", "._thumb"),
|
||||
}); err != nil {
|
||||
util.Log().Warning("Failed to save thumb for %s: %s", file.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
//"github.com/nfnt/resize"
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
@@ -156,14 +156,30 @@ func (image *Thumb) CreateAvatar(uid uint) error {
|
||||
|
||||
type Builtin struct{}
|
||||
|
||||
func (b Builtin) Generate(file io.Reader, w io.Writer, name string, options map[string]string) error {
|
||||
func (b Builtin) Generate(file io.Reader, name string, options map[string]string) (string, error) {
|
||||
img, err := NewThumbFromFile(file, name)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
img.GetThumb(thumbSize(options))
|
||||
return img.Save(w)
|
||||
tempPath := filepath.Join(
|
||||
util.RelativePath(model.GetSettingByName("temp_path")),
|
||||
"thumb",
|
||||
fmt.Sprintf("thumb_%s", uuid.Must(uuid.NewV4()).String()),
|
||||
)
|
||||
|
||||
thumbFile, err := util.CreatNestedFile(tempPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create temp file: %w", err)
|
||||
}
|
||||
|
||||
defer thumbFile.Close()
|
||||
if err := img.Save(thumbFile); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tempPath, nil
|
||||
}
|
||||
|
||||
func (b Builtin) Priority() int {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// Generator generates a thumbnail for a given reader.
|
||||
type Generator interface {
|
||||
Generate(file io.Reader, w io.Writer, name string, options map[string]string) error
|
||||
Generate(file io.Reader, name string, options map[string]string) (string, error)
|
||||
|
||||
// Priority of execution order, smaller value means higher priority.
|
||||
Priority() int
|
||||
@@ -51,19 +51,19 @@ func RegisterGenerator(generator Generator) {
|
||||
sort.Sort(Generators)
|
||||
}
|
||||
|
||||
func (p GeneratorList) Generate(file io.Reader, w io.Writer, name string, options map[string]string) error {
|
||||
func (p GeneratorList) Generate(file io.Reader, name string, options map[string]string) (string, error) {
|
||||
for _, generator := range p {
|
||||
if model.IsTrueVal(options[generator.EnableFlag()]) {
|
||||
err := generator.Generate(file, w, name, options)
|
||||
res, err := generator.Generate(file, name, options)
|
||||
if errors.Is(err, ErrPassThrough) {
|
||||
util.Log().Debug("Failed to generate thumbnail for %s: %s, passing through to next generator.", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
return ErrNotAvailable
|
||||
return "", ErrNotAvailable
|
||||
}
|
||||
|
||||
func (p GeneratorList) Priority() int {
|
||||
|
||||
Reference in New Issue
Block a user