ref: 3fbc75534d1acda2be1c597aa77c919d3a02659d
parent: e427ba4268603de7b096f48e7dbda40e03750911
author: Bjørn Erik Pedersen <[email protected]>
date: Wed Mar 14 05:33:32 EDT 2018
resource: Fix path duplication/flattening in processed images Fixes #4502 Closes #4501
--- a/resource/image.go
+++ b/resource/image.go
@@ -209,7 +209,7 @@
}
func (i *Image) isJPEG() bool {
- name := strings.ToLower(i.relTargetPath)
+ name := strings.ToLower(i.relTargetPath.file)
return strings.HasSuffix(name, ".jpg") || strings.HasSuffix(name, ".jpeg")
}
@@ -237,9 +237,7 @@
}
}
- key := i.relTargetPathForRel(i.filenameFromConfig(conf), false)
-
- return i.spec.imageCache.getOrCreate(i, key, func(resourceCacheFilename string) (*Image, error) {
+ return i.spec.imageCache.getOrCreate(i, conf, func(resourceCacheFilename string) (*Image, error) {
ci := i.clone()
errOp := action
@@ -449,7 +447,6 @@
func (i *Image) copyToDestination(src string) error {
var res error
-
i.copyToDestinationInit.Do(func() {
target := filepath.Join(i.absPublishDir, i.target())
@@ -498,7 +495,6 @@
}
func (i *Image) encodeToDestinations(img image.Image, conf imageConfig, resourceCacheFilename, filename string) error {
-
target := filepath.Join(i.absPublishDir, filename)
file1, err := i.spec.Fs.Destination.Create(target)
@@ -574,11 +570,12 @@
}
func (i *Image) setBasePath(conf imageConfig) {
- i.relTargetPath = i.filenameFromConfig(conf)
+ i.relTargetPath = i.relTargetPathFromConfig(conf)
}
-func (i *Image) filenameFromConfig(conf imageConfig) string {
- p1, p2 := helpers.FileAndExt(i.relTargetPath)
+func (i *Image) relTargetPathFromConfig(conf imageConfig) dirFile {
+ p1, p2 := helpers.FileAndExt(i.relTargetPath.file)
+
idStr := fmt.Sprintf("_hu%s_%d", i.hash, i.osFileInfo.Size())
// Do not change for no good reason.
@@ -605,7 +602,11 @@
idStr = ""
}
- return fmt.Sprintf("%s%s_%s%s", p1, idStr, key, p2)
+ return dirFile{
+ dir: i.relTargetPath.dir,
+ file: fmt.Sprintf("%s%s_%s%s", p1, idStr, key, p2),
+ }
+
}
func decodeImaging(m map[string]interface{}) (Imaging, error) {
--- a/resource/image_cache.go
+++ b/resource/image_cache.go
@@ -49,10 +49,17 @@
}
}
+func (c *imageCache) clear() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.store = make(map[string]*Image)
+}
+
func (c *imageCache) getOrCreate(
- parent *Image, key string, create func(resourceCacheFilename string) (*Image, error)) (*Image, error) {
+ parent *Image, conf imageConfig, create func(resourceCacheFilename string) (*Image, error)) (*Image, error) {
- relTargetFilename := key
+ relTarget := parent.relTargetPathFromConfig(conf)
+ key := parent.relTargetPathForRel(relTarget.path(), false)
if c.pathSpec.Language != nil {
// Avoid do and store more work than needed. The language versions will in
@@ -89,7 +96,7 @@
if exists {
img = parent.clone()
- img.relTargetPath = relTargetFilename
+ img.relTargetPath.file = relTarget.file
img.absSourceFilename = cacheFilename
} else {
img, err = create(cacheFilename)
--- a/resource/image_test.go
+++ b/resource/image_test.go
@@ -16,6 +16,7 @@
import (
"fmt"
"math/rand"
+ "path/filepath"
"strconv"
"testing"
@@ -275,6 +276,39 @@
assert.Equal(imaging.PNG, resized.format)
assert.Equal("/a/gohugoio_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_800x0_resize_linear_2.png", resized.RelPermalink())
assert.Equal(800, resized.Width())
+
+}
+
+func TestImageResizeInSubPath(t *testing.T) {
+
+ assert := require.New(t)
+
+ image := fetchImage(assert, "sub/gohugoio2.png")
+
+ assert.Equal(imaging.PNG, image.format)
+ assert.Equal("/a/sub/gohugoio2.png", image.RelPermalink())
+ assert.Equal("image", image.ResourceType())
+
+ resized, err := image.Resize("101x101")
+ assert.NoError(err)
+ assert.Equal(imaging.PNG, resized.format)
+ assert.Equal("/a/sub/gohugoio2_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_101x101_resize_linear_2.png", resized.RelPermalink())
+ assert.Equal(101, resized.Width())
+
+ assertFileCache(assert, image.spec.Fs, resized.RelPermalink(), 101, 101)
+ publishedImageFilename := filepath.Join("/public", resized.RelPermalink())
+ assertImageFile(assert, image.spec.Fs, publishedImageFilename, 101, 101)
+ assert.NoError(image.spec.Fs.Destination.Remove(publishedImageFilename))
+
+ // Cleare mem cache to simulate reading from the file cache.
+ resized.spec.imageCache.clear()
+
+ resizedAgain, err := image.Resize("101x101")
+ assert.NoError(err)
+ assert.Equal("/a/sub/gohugoio2_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_101x101_resize_linear_2.png", resizedAgain.RelPermalink())
+ assert.Equal(101, resizedAgain.Width())
+ assertFileCache(assert, image.spec.Fs, resizedAgain.RelPermalink(), 101, 101)
+ assertImageFile(assert, image.spec.Fs, publishedImageFilename, 101, 101)
}
--- a/resource/resource.go
+++ b/resource/resource.go
@@ -283,7 +283,7 @@
}
}
- gr := r.newGenericResource(targetPathBuilder, fi, absPublishDir, absSourceFilename, filepath.ToSlash(relTargetFilename), mimeType)
+ gr := r.newGenericResource(targetPathBuilder, fi, absPublishDir, absSourceFilename, relTargetFilename, mimeType)
if mimeType == "image" {
ext := strings.ToLower(helpers.Ext(absSourceFilename))
@@ -343,11 +343,24 @@
return s
}
+type dirFile struct {
+ // This is the directory component with Unix-style slashes.
+ dir string
+ // This is the file component.
+ file string
+}
+
+func (d dirFile) path() string {
+ return path.Join(d.dir, d.file)
+}
+
// genericResource represents a generic linkable resource.
type genericResource struct {
// The relative path to this resource.
- relTargetPath string
+ relTargetPath dirFile
+ file string
+
// Base is set when the output format's path has a offset, e.g. for AMP.
base string
@@ -366,11 +379,11 @@
}
func (l *genericResource) Permalink() string {
- return l.spec.PermalinkForBaseURL(l.relPermalinkForRel(l.relTargetPath, false), l.spec.BaseURL.String())
+ return l.spec.PermalinkForBaseURL(l.relPermalinkForRel(l.relTargetPath.path(), false), l.spec.BaseURL.String())
}
func (l *genericResource) RelPermalink() string {
- return l.relPermalinkForRel(l.relTargetPath, true)
+ return l.relPermalinkForRel(l.relTargetPath.path(), true)
}
func (l *genericResource) Name() string {
@@ -551,11 +564,11 @@
}
func (l *genericResource) target() string {
- target := l.relTargetPathForRel(l.relTargetPath, false)
+ target := l.relTargetPathForRel(l.relTargetPath.path(), false)
if l.spec.PathSpec.Languages.IsMultihost() {
target = path.Join(l.spec.PathSpec.Language.Lang, target)
}
- return target
+ return filepath.Clean(target)
}
func (r *Spec) newGenericResource(
@@ -566,12 +579,17 @@
baseFilename,
resourceType string) *genericResource {
+ // This value is used both to construct URLs and file paths, but start
+ // with a Unix-styled path.
+ baseFilename = filepath.ToSlash(baseFilename)
+ fpath, fname := path.Split(baseFilename)
+
return &genericResource{
targetPathBuilder: targetPathBuilder,
osFileInfo: osFileInfo,
absPublishDir: absPublishDir,
absSourceFilename: absSourceFilename,
- relTargetPath: baseFilename,
+ relTargetPath: dirFile{dir: fpath, file: fname},
resourceType: resourceType,
spec: r,
params: make(map[string]interface{}),
--- a/resource/resource_test.go
+++ b/resource/resource_test.go
@@ -93,7 +93,7 @@
assert.Equal("/docs/a/b/logo.png", r.RelPermalink())
assert.Equal("https://example.com/docs/a/b/logo.png", r.Permalink())
img := r.(*Image)
- assert.Equal("/a/b/logo.png", img.target())
+ assert.Equal(filepath.FromSlash("/a/b/logo.png"), img.target())
}
binary files /dev/null b/resource/testdata/sub/gohugoio2.png differ
--- a/resource/testhelpers_test.go
+++ b/resource/testhelpers_test.go
@@ -94,7 +94,7 @@
}
func fetchResourceForSpec(spec *Spec, assert *require.Assertions, name string) Resource {
- src, err := os.Open("testdata/" + name)
+ src, err := os.Open(filepath.FromSlash("testdata/" + name))
assert.NoError(err)
workingDir := spec.Cfg.GetString("workingDir")
@@ -116,8 +116,9 @@
return r
}
-func assertFileCache(assert *require.Assertions, fs *hugofs.Fs, filename string, width, height int) {
- f, err := fs.Source.Open(filepath.Join("/res/_gen/images", filename))
+
+func assertImageFile(assert *require.Assertions, fs *hugofs.Fs, filename string, width, height int) {
+ f, err := fs.Source.Open(filename)
assert.NoError(err)
defer f.Close()
@@ -126,6 +127,10 @@
assert.Equal(width, config.Width)
assert.Equal(height, config.Height)
+}
+
+func assertFileCache(assert *require.Assertions, fs *hugofs.Fs, filename string, width, height int) {
+ assertImageFile(assert, fs, filepath.Join("/res/_gen/images", filename), width, height)
}
func writeSource(t testing.TB, fs *hugofs.Fs, filename, content string) {