mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-13 20:24:00 +02:00
resources: Fix 2 image file cache key issues
* Always include the content hash in the cache key for unprocessed images. * Always include the image config hash in the cache key. This is also a major cleanup/simplification of the implementation in this area. Note that this, unfortunately, forces new hashes/filenames for generated images. Fixes #13273 Fixes #13272
This commit is contained in:
@@ -30,7 +30,6 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/disintegration/gift"
|
||||
@@ -205,15 +204,12 @@ func (i *imageResource) cloneWithUpdates(u *transformationUpdate) (baseResource,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var imageActions = []string{images.ActionResize, images.ActionCrop, images.ActionFit, images.ActionFill}
|
||||
|
||||
// Process processes the image with the given spec.
|
||||
// The spec can contain an optional action, one of "resize", "crop", "fit" or "fill".
|
||||
// This makes this method a more flexible version that covers all of Resize, Crop, Fit and Fill,
|
||||
// but it also supports e.g. format conversions without any resize action.
|
||||
func (i *imageResource) Process(spec string) (images.ImageResource, error) {
|
||||
action, options := i.resolveActionOptions(spec)
|
||||
return i.processActionOptions(action, options)
|
||||
return i.processActionSpec("", spec)
|
||||
}
|
||||
|
||||
// Resize resizes the image to the specified width and height using the specified resampling
|
||||
@@ -243,7 +239,7 @@ func (i *imageResource) Fill(spec string) (images.ImageResource, error) {
|
||||
}
|
||||
|
||||
func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
|
||||
var conf images.ImageConfig
|
||||
var confMain images.ImageConfig
|
||||
|
||||
var gfilters []gift.Filter
|
||||
|
||||
@@ -251,47 +247,30 @@ func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
|
||||
gfilters = append(gfilters, images.ToFilters(f)...)
|
||||
}
|
||||
|
||||
var (
|
||||
targetFormat images.Format
|
||||
configSet bool
|
||||
)
|
||||
var options []string
|
||||
|
||||
for _, f := range gfilters {
|
||||
f = images.UnwrapFilter(f)
|
||||
if specProvider, ok := f.(images.ImageProcessSpecProvider); ok {
|
||||
action, options := i.resolveActionOptions(specProvider.ImageProcessSpec())
|
||||
var err error
|
||||
conf, err = images.DecodeImageConfig(action, options, i.Proc.Cfg, i.Format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configSet = true
|
||||
if conf.TargetFormat != 0 {
|
||||
targetFormat = conf.TargetFormat
|
||||
// We only support one target format, but prefer the last one,
|
||||
// so we keep going.
|
||||
}
|
||||
options = append(options, strings.Fields(specProvider.ImageProcessSpec())...)
|
||||
}
|
||||
}
|
||||
|
||||
if !configSet {
|
||||
conf = images.GetDefaultImageConfig("filter", i.Proc.Cfg)
|
||||
confMain, err := images.DecodeImageConfig(options, i.Proc.Cfg, i.Format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.Action = "filter"
|
||||
conf.Key = hashing.HashString(gfilters)
|
||||
conf.TargetFormat = targetFormat
|
||||
if conf.TargetFormat == 0 {
|
||||
conf.TargetFormat = i.Format
|
||||
}
|
||||
confMain.Action = "filter"
|
||||
confMain.Key = hashing.HashString(gfilters)
|
||||
|
||||
return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
|
||||
return i.doWithImageConfig(confMain, func(src image.Image) (image.Image, error) {
|
||||
var filters []gift.Filter
|
||||
for _, f := range gfilters {
|
||||
f = images.UnwrapFilter(f)
|
||||
if specProvider, ok := f.(images.ImageProcessSpecProvider); ok {
|
||||
processSpec := specProvider.ImageProcessSpec()
|
||||
action, options := i.resolveActionOptions(processSpec)
|
||||
conf, err := images.DecodeImageConfig(action, options, i.Proc.Cfg, i.Format)
|
||||
options := strings.Fields(specProvider.ImageProcessSpec())
|
||||
conf, err := images.DecodeImageConfig(options, i.Proc.Cfg, i.Format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -313,25 +292,13 @@ func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (i *imageResource) resolveActionOptions(spec string) (string, []string) {
|
||||
var action string
|
||||
options := strings.Fields(spec)
|
||||
for i, p := range options {
|
||||
if hstrings.InSlicEqualFold(imageActions, p) {
|
||||
action = p
|
||||
options = append(options[:i], options[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return action, options
|
||||
}
|
||||
|
||||
func (i *imageResource) processActionSpec(action, spec string) (images.ImageResource, error) {
|
||||
return i.processActionOptions(action, strings.Fields(spec))
|
||||
options := append([]string{action}, strings.Fields(strings.ToLower(spec))...)
|
||||
return i.processOptions(options)
|
||||
}
|
||||
|
||||
func (i *imageResource) processActionOptions(action string, options []string) (images.ImageResource, error) {
|
||||
conf, err := images.DecodeImageConfig(action, options, i.Proc.Cfg, i.Format)
|
||||
func (i *imageResource) processOptions(options []string) (images.ImageResource, error) {
|
||||
conf, err := images.DecodeImageConfig(options, i.Proc.Cfg, i.Format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -343,13 +310,12 @@ func (i *imageResource) processActionOptions(action string, options []string) (i
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if action == images.ActionFill {
|
||||
if conf.Anchor == 0 && img.Width() == 0 || img.Height() == 0 {
|
||||
if conf.Action == images.ActionFill {
|
||||
if conf.Anchor == images.SmartCropAnchor && img.Width() == 0 || img.Height() == 0 {
|
||||
// See https://github.com/gohugoio/hugo/issues/7955
|
||||
// Smartcrop fails silently in some rare cases.
|
||||
// Fall back to a center fill.
|
||||
conf.Anchor = gift.CenterAnchor
|
||||
conf.AnchorStr = "center"
|
||||
conf = conf.Reanchor(gift.CenterAnchor)
|
||||
return i.doWithImageConfig(conf, func(src image.Image) (image.Image, error) {
|
||||
return i.Proc.ApplyFiltersFromConfig(src, conf)
|
||||
})
|
||||
@@ -417,7 +383,7 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im
|
||||
}
|
||||
|
||||
ci := i.clone(converted)
|
||||
targetPath := i.relTargetPathFromConfig(conf)
|
||||
targetPath := i.relTargetPathFromConfig(conf, i.getSpec().imaging.Cfg.SourceHash)
|
||||
ci.setTargetPath(targetPath)
|
||||
ci.Format = conf.TargetFormat
|
||||
ci.setMediaType(conf.TargetFormat.MediaType())
|
||||
@@ -485,26 +451,30 @@ func (i *imageResource) getImageMetaCacheTargetPath() string {
|
||||
df := i.getResourcePaths()
|
||||
p1, _ := paths.FileAndExt(df.File)
|
||||
h := i.hash()
|
||||
idStr := hashing.HashString(h, i.size(), imageMetaVersionNumber, cfgHash)
|
||||
idStr := hashing.HashStringHex(h, i.size(), imageMetaVersionNumber, cfgHash)
|
||||
df.File = fmt.Sprintf("%s_%s.json", p1, idStr)
|
||||
return df.TargetPath()
|
||||
}
|
||||
|
||||
func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) internal.ResourcePaths {
|
||||
func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig, imagingConfigSourceHash string) internal.ResourcePaths {
|
||||
p1, p2 := paths.FileAndExt(i.getResourcePaths().File)
|
||||
if conf.TargetFormat != i.Format {
|
||||
p2 = conf.TargetFormat.DefaultExtension()
|
||||
}
|
||||
const prefix = "_hu"
|
||||
huIdx := strings.LastIndex(p1, prefix)
|
||||
incomingID := "i"
|
||||
|
||||
// Do not change.
|
||||
const imageHashPrefix = "_hu_"
|
||||
|
||||
huIdx := strings.LastIndex(p1, imageHashPrefix)
|
||||
incomingID := ""
|
||||
if huIdx > -1 {
|
||||
incomingID = p1[huIdx+len(prefix):]
|
||||
incomingID = p1[huIdx+len(imageHashPrefix):]
|
||||
p1 = p1[:huIdx]
|
||||
}
|
||||
hash := hashing.HashUint64(incomingID, i.hash(), conf.GetKey(i.Format))
|
||||
|
||||
hash := hashing.HashStringHex(incomingID, i.hash(), conf.Key, imagingConfigSourceHash)
|
||||
rp := i.getResourcePaths()
|
||||
rp.File = fmt.Sprintf("%s%s%d%s", p1, prefix, hash, p2)
|
||||
rp.File = fmt.Sprintf("%s%s%s%s", p1, imageHashPrefix, hash, p2)
|
||||
|
||||
return rp
|
||||
}
|
||||
|
Reference in New Issue
Block a user