shithub: hugo

Download patch

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) {