mirror of
https://github.com/cloudreve/Cloudreve.git
synced 2025-12-26 00:12:50 +00:00
feat(thumb blob path): support magic variables in thumb blob path (#3030)
This commit is contained in:
parent
6085f2090f
commit
deecc5c20b
|
|
@ -477,6 +477,23 @@ var patches = []Patch{
|
||||||
return fmt.Errorf("failed to update mail_reset_template setting: %w", err)
|
return fmt.Errorf("failed to update mail_reset_template setting: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "apply_thumb_path_magic_var",
|
||||||
|
EndVersion: "4.10.0",
|
||||||
|
Func: func(l logging.Logger, client *ent.Client, ctx context.Context) error {
|
||||||
|
thumbSuffixSetting, err := client.Setting.Query().Where(setting.Name("thumb_entity_suffix")).First(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to query thumb_entity_suffix setting: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newThumbSuffix := fmt.Sprintf("{blob_path}/{blob_name}%s", thumbSuffixSetting.Value)
|
||||||
|
if _, err := client.Setting.UpdateOne(thumbSuffixSetting).SetValue(newThumbSuffix).Save(ctx); err != nil {
|
||||||
|
return fmt.Errorf("failed to update thumb_entity_suffix setting: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -555,7 +555,7 @@ var DefaultSettings = map[string]string{
|
||||||
"captcha_cap_asset_server": "jsdelivr",
|
"captcha_cap_asset_server": "jsdelivr",
|
||||||
"thumb_width": "400",
|
"thumb_width": "400",
|
||||||
"thumb_height": "300",
|
"thumb_height": "300",
|
||||||
"thumb_entity_suffix": "._thumb",
|
"thumb_entity_suffix": "{blob_path}/{blob_name}._thumb",
|
||||||
"thumb_slave_sidecar_suffix": "._thumb_sidecar",
|
"thumb_slave_sidecar_suffix": "._thumb_sidecar",
|
||||||
"thumb_encode_method": "png",
|
"thumb_encode_method": "png",
|
||||||
"thumb_gc_after_gen": "0",
|
"thumb_gc_after_gen": "0",
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -126,7 +122,7 @@ func (f *DBFS) List(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fi
|
||||||
|
|
||||||
parent, err := f.getFileByPath(ctx, navigator, path)
|
parent, err := f.getFileByPath(ctx, navigator, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("Parent not exist: %w", err)
|
return nil, nil, fmt.Errorf("parent not exist: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pageSize := 0
|
pageSize := 0
|
||||||
|
|
@ -787,71 +783,16 @@ func (f *DBFS) navigatorId(path *fs.URI) string {
|
||||||
// generateSavePath generates the physical save path for the upload request.
|
// generateSavePath generates the physical save path for the upload request.
|
||||||
func generateSavePath(policy *ent.StoragePolicy, req *fs.UploadRequest, user *ent.User) string {
|
func generateSavePath(policy *ent.StoragePolicy, req *fs.UploadRequest, user *ent.User) string {
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
originName := req.Props.Uri.Name()
|
dynamicReplace := func(rule string, pathAvailable bool) string {
|
||||||
|
return util.ReplaceMagicVar(rule, fs.Separator, pathAvailable, false, currentTime, user.ID, req.Props.Uri.Name(), req.Props.Uri.Dir(), "")
|
||||||
dynamicReplace := func(regPattern string, rule string, pathAvailable bool) string {
|
|
||||||
re := regexp.MustCompile(regPattern)
|
|
||||||
return re.ReplaceAllStringFunc(rule, func(match string) string {
|
|
||||||
switch match {
|
|
||||||
case "{timestamp}":
|
|
||||||
return strconv.FormatInt(currentTime.Unix(), 10)
|
|
||||||
case "{timestamp_nano}":
|
|
||||||
return strconv.FormatInt(currentTime.UnixNano(), 10)
|
|
||||||
case "{datetime}":
|
|
||||||
return currentTime.Format("20060102150405")
|
|
||||||
case "{date}":
|
|
||||||
return currentTime.Format("20060102")
|
|
||||||
case "{year}":
|
|
||||||
return currentTime.Format("2006")
|
|
||||||
case "{month}":
|
|
||||||
return currentTime.Format("01")
|
|
||||||
case "{day}":
|
|
||||||
return currentTime.Format("02")
|
|
||||||
case "{hour}":
|
|
||||||
return currentTime.Format("15")
|
|
||||||
case "{minute}":
|
|
||||||
return currentTime.Format("04")
|
|
||||||
case "{second}":
|
|
||||||
return currentTime.Format("05")
|
|
||||||
case "{uid}":
|
|
||||||
return strconv.Itoa(user.ID)
|
|
||||||
case "{randomkey16}":
|
|
||||||
return util.RandStringRunes(16)
|
|
||||||
case "{randomkey8}":
|
|
||||||
return util.RandStringRunes(8)
|
|
||||||
case "{randomnum8}":
|
|
||||||
return strconv.Itoa(rand.Intn(8))
|
|
||||||
case "{randomnum4}":
|
|
||||||
return strconv.Itoa(rand.Intn(4))
|
|
||||||
case "{randomnum3}":
|
|
||||||
return strconv.Itoa(rand.Intn(3))
|
|
||||||
case "{randomnum2}":
|
|
||||||
return strconv.Itoa(rand.Intn(2))
|
|
||||||
case "{uuid}":
|
|
||||||
return uuid.Must(uuid.NewV4()).String()
|
|
||||||
case "{path}":
|
|
||||||
if pathAvailable {
|
|
||||||
return req.Props.Uri.Dir() + fs.Separator
|
|
||||||
}
|
|
||||||
return match
|
|
||||||
case "{originname}":
|
|
||||||
return originName
|
|
||||||
case "{ext}":
|
|
||||||
return filepath.Ext(originName)
|
|
||||||
case "{originname_without_ext}":
|
|
||||||
return strings.TrimSuffix(originName, filepath.Ext(originName))
|
|
||||||
default:
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dirRule := policy.DirNameRule
|
dirRule := policy.DirNameRule
|
||||||
dirRule = filepath.ToSlash(dirRule)
|
dirRule = filepath.ToSlash(dirRule)
|
||||||
dirRule = dynamicReplace(`\{[^{}]+\}`, dirRule, true)
|
dirRule = dynamicReplace(dirRule, true)
|
||||||
|
|
||||||
nameRule := policy.FileNameRule
|
nameRule := policy.FileNameRule
|
||||||
nameRule = dynamicReplace(`\{[^{}]+\}`, nameRule, false)
|
nameRule = dynamicReplace(nameRule, false)
|
||||||
|
|
||||||
return path.Join(path.Clean(dirRule), nameRule)
|
return path.Join(path.Clean(dirRule), nameRule)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ func (f *DBFS) PrepareUpload(ctx context.Context, req *fs.UploadRequest, opts ..
|
||||||
if req.Props.SavePath == "" || isThumbnailAndPolicyNotAvailable {
|
if req.Props.SavePath == "" || isThumbnailAndPolicyNotAvailable {
|
||||||
req.Props.SavePath = generateSavePath(policy, req, f.user)
|
req.Props.SavePath = generateSavePath(policy, req, f.user)
|
||||||
if isThumbnailAndPolicyNotAvailable {
|
if isThumbnailAndPolicyNotAvailable {
|
||||||
req.Props.SavePath = req.Props.SavePath + f.settingClient.ThumbEntitySuffix(ctx)
|
req.Props.SavePath = path.Clean(util.ReplaceMagicVar(f.settingClient.ThumbEntitySuffix(ctx), fs.Separator, true, true, time.Now(), f.user.ID, req.Props.Uri.Name(), req.Props.Uri.Path(), req.Props.SavePath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/thumb"
|
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager/entitysource"
|
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager/entitysource"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/queue"
|
"github.com/cloudreve/Cloudreve/v4/pkg/queue"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/pkg/thumb"
|
||||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
@ -185,7 +186,7 @@ func (m *manager) generateThumb(ctx context.Context, uri *fs.URI, ext string, es
|
||||||
Props: &fs.UploadProps{
|
Props: &fs.UploadProps{
|
||||||
Uri: uri,
|
Uri: uri,
|
||||||
Size: fileInfo.Size(),
|
Size: fileInfo.Size(),
|
||||||
SavePath: es.Entity().Source() + m.settings.ThumbEntitySuffix(ctx),
|
SavePath: path.Clean(util.ReplaceMagicVar(m.settings.ThumbEntitySuffix(ctx), fs.Separator, true, true, time.Now(), m.user.ID, uri.Name(), uri.Path(), es.Entity().Source())),
|
||||||
MimeType: m.dep.MimeDetector(ctx).TypeByName("thumb.jpg"),
|
MimeType: m.dep.MimeDetector(ctx).TypeByName("thumb.jpg"),
|
||||||
EntityType: &entityType,
|
EntityType: &entityType,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -510,7 +510,7 @@ func (s *settingProvider) ThumbEncode(ctx context.Context) *ThumbEncode {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settingProvider) ThumbEntitySuffix(ctx context.Context) string {
|
func (s *settingProvider) ThumbEntitySuffix(ctx context.Context) string {
|
||||||
return s.getString(ctx, "thumb_entity_suffix", "._thumb")
|
return s.getString(ctx, "thumb_entity_suffix", "{blob_path}/{blob_name}._thumb")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *settingProvider) ThumbSlaveSidecarSuffix(ctx context.Context) string {
|
func (s *settingProvider) ThumbSlaveSidecarSuffix(ctx context.Context) string {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,16 @@ package util
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -95,6 +99,80 @@ func Replace(table map[string]string, s string) string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReplaceMagicVar 动态替换字符串中的魔法变量
|
||||||
|
func ReplaceMagicVar(rawString string, fsSeparator string, pathAvailable bool, blobAvailable bool,
|
||||||
|
timeConst time.Time, userId int, originName string, originPath string, completeBlobPath string) string {
|
||||||
|
re := regexp.MustCompile(`\{[^{}]+\}`)
|
||||||
|
return re.ReplaceAllStringFunc(rawString, func(match string) string {
|
||||||
|
switch match {
|
||||||
|
case "{randomkey16}":
|
||||||
|
return RandStringRunes(16)
|
||||||
|
case "{randomkey8}":
|
||||||
|
return RandStringRunes(8)
|
||||||
|
case "{timestamp}":
|
||||||
|
return strconv.FormatInt(timeConst.Unix(), 10)
|
||||||
|
case "{timestamp_nano}":
|
||||||
|
return strconv.FormatInt(timeConst.UnixNano(), 10)
|
||||||
|
case "{randomnum2}":
|
||||||
|
return strconv.Itoa(rand.Intn(2))
|
||||||
|
case "{randomnum3}":
|
||||||
|
return strconv.Itoa(rand.Intn(3))
|
||||||
|
case "{randomnum4}":
|
||||||
|
return strconv.Itoa(rand.Intn(4))
|
||||||
|
case "{randomnum8}":
|
||||||
|
return strconv.Itoa(rand.Intn(8))
|
||||||
|
case "{uid}":
|
||||||
|
return strconv.Itoa(userId)
|
||||||
|
case "{datetime}":
|
||||||
|
return timeConst.Format("20060102150405")
|
||||||
|
case "{date}":
|
||||||
|
return timeConst.Format("20060102")
|
||||||
|
case "{year}":
|
||||||
|
return timeConst.Format("2006")
|
||||||
|
case "{month}":
|
||||||
|
return timeConst.Format("01")
|
||||||
|
case "{day}":
|
||||||
|
return timeConst.Format("02")
|
||||||
|
case "{hour}":
|
||||||
|
return timeConst.Format("15")
|
||||||
|
case "{minute}":
|
||||||
|
return timeConst.Format("04")
|
||||||
|
case "{second}":
|
||||||
|
return timeConst.Format("05")
|
||||||
|
case "{uuid}":
|
||||||
|
return uuid.Must(uuid.NewV4()).String()
|
||||||
|
case "{ext}":
|
||||||
|
return filepath.Ext(originName)
|
||||||
|
case "{originname}":
|
||||||
|
return originName
|
||||||
|
case "{originname_without_ext}":
|
||||||
|
return strings.TrimSuffix(originName, filepath.Ext(originName))
|
||||||
|
case "{path}":
|
||||||
|
if pathAvailable {
|
||||||
|
return originPath + fsSeparator
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
case "{blob_name}":
|
||||||
|
if blobAvailable {
|
||||||
|
return filepath.Base(completeBlobPath)
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
case "{blob_name_without_ext}":
|
||||||
|
if blobAvailable {
|
||||||
|
return strings.TrimSuffix(filepath.Base(completeBlobPath), filepath.Ext(completeBlobPath))
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
case "{blob_path}":
|
||||||
|
if blobAvailable {
|
||||||
|
return filepath.Dir(completeBlobPath) + fsSeparator
|
||||||
|
}
|
||||||
|
return match
|
||||||
|
default:
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// BuildRegexp 构建用于SQL查询用的多条件正则
|
// BuildRegexp 构建用于SQL查询用的多条件正则
|
||||||
func BuildRegexp(search []string, prefix, suffix, condition string) string {
|
func BuildRegexp(search []string, prefix, suffix, condition string) string {
|
||||||
var res string
|
var res string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue