ref: 8624b9fe9eb81aeb884d36311fb6f85fed98aa43
parent: 018494f363a32b9e4d3622da6842bc3e59b420b2
author: Bjørn Erik Pedersen <[email protected]>
date: Tue Sep 3 06:36:09 EDT 2019
Cache processed images by their source path Fixes #6269
--- a/hugolib/image_test.go
+++ b/hugolib/image_test.go
@@ -14,7 +14,6 @@
package hugolib
import (
- "image/jpeg"
"io"
"os"
"path/filepath"
@@ -100,16 +99,6 @@
b := newBuilder()
b.Build(BuildCfg{})
- assertImage := func(width, height int, filename string) {
- filename = filepath.Join(workDir, "public", filename)
- f, err := b.Fs.Destination.Open(filename)
- c.Assert(err, qt.IsNil)
- defer f.Close()
- cfg, err := jpeg.DecodeConfig(f)
- c.Assert(cfg.Width, qt.Equals, width)
- c.Assert(cfg.Height, qt.Equals, height)
- }
-
imgExpect := `
Resized1: images/sunset.jpg|123|234|image/jpg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg|
Resized2: images/sunset.jpg|12|23|image/jpg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ada4bb1a57f77a63306e3bd67286248e.jpg|
@@ -121,7 +110,7 @@
`
b.AssertFileContent(filepath.Join(workDir, "public/index.html"), imgExpect)
- assertImage(350, 219, "images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg")
+ b.AssertImage(350, 219, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg")
// Build it again to make sure we read images from file cache.
b = newBuilder()
@@ -128,5 +117,78 @@
b.Build(BuildCfg{})
b.AssertFileContent(filepath.Join(workDir, "public/index.html"), imgExpect)
+
+}
+
+func TestImageResizeMultilingual(t *testing.T) {
+
+ b := newTestSitesBuilder(t).WithConfigFile("toml", `
+baseURL="https://example.org"
+defaultContentLanguage = "en"
+
+[languages]
+[languages.en]
+title = "Title in English"
+languageName = "English"
+weight = 1
+[languages.nn]
+languageName = "Nynorsk"
+weight = 2
+title = "Tittel på nynorsk"
+[languages.nb]
+languageName = "Bokmål"
+weight = 3
+title = "Tittel på bokmål"
+[languages.fr]
+languageName = "French"
+weight = 4
+title = "French Title"
+
+`)
+
+ pageContent := `---
+title: "Page"
+---
+`
+
+ b.WithContent("bundle/index.md", pageContent)
+ b.WithContent("bundle/index.nn.md", pageContent)
+ b.WithContent("bundle/index.fr.md", pageContent)
+ b.WithSunset("content/bundle/sunset.jpg")
+ b.WithSunset("assets/images/sunset.jpg")
+ b.WithTemplates("index.html", `
+{{ with (.Site.GetPage "bundle" ) }}
+{{ $sunset := .Resources.GetMatch "sunset*" }}
+{{ if $sunset }}
+{{ $resized := $sunset.Resize "200x200" }}
+SUNSET FOR: {{ $.Site.Language.Lang }}: {{ $resized.RelPermalink }}/{{ $resized.Width }}/Lat: {{ $resized.Exif.Lat }}
+{{ end }}
+{{ else }}
+No bundle for {{ $.Site.Language.Lang }}
+{{ end }}
+
+{{ $sunset2 := resources.Get "images/sunset.jpg" }}
+{{ $resized2 := $sunset2.Resize "123x234" }}
+SUNSET2: {{ $resized2.RelPermalink }}/{{ $resized2.Width }}/Lat: {{ $resized2.Exif.Lat }}
+
+`)
+
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent("public/index.html", "SUNSET FOR: en: /bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg/200/Lat: 36.59744166666667")
+ b.AssertFileContent("public/fr/index.html", "SUNSET FOR: fr: /fr/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg/200/Lat: 36.59744166666667")
+ b.AssertFileContent("public/index.html", " SUNSET2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg/123/Lat: 36.59744166666667")
+ b.AssertFileContent("public/nn/index.html", " SUNSET2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg/123/Lat: 36.59744166666667")
+
+ b.AssertImage(200, 200, "public/fr/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
+ b.AssertImage(200, 200, "public/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
+
+ // Check the file cache
+ b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
+ b.AssertFileContent("resources/_gen/images/bundle/sunset_17701188623491591036.json",
+ "DateTimeDigitized|time.Time", "PENTAX")
+ b.AssertImage(123, 234, "resources/_gen/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg")
+ b.AssertFileContent("resources/_gen/images/sunset_17701188623491591036.json",
+ "DateTimeDigitized|time.Time", "PENTAX")
}
--- a/hugolib/testhelpers_test.go
+++ b/hugolib/testhelpers_test.go
@@ -1,6 +1,7 @@
package hugolib
import (
+ "image/jpeg"
"io"
"path/filepath"
"runtime"
@@ -334,7 +335,7 @@
src, err := os.Open(filepath.FromSlash("testdata/sunset.jpg"))
s.Assert(err, qt.IsNil)
- out, err := s.Fs.Source.Create(filepath.FromSlash(in))
+ out, err := s.Fs.Source.Create(filepath.FromSlash(filepath.Join(s.workingDir, in)))
s.Assert(err, qt.IsNil)
_, err = io.Copy(out, src)
@@ -667,6 +668,16 @@
}
}
}
+}
+
+func (s *sitesBuilder) AssertImage(width, height int, filename string) {
+ filename = filepath.Join(s.workingDir, filename)
+ f, err := s.Fs.Destination.Open(filename)
+ s.Assert(err, qt.IsNil)
+ defer f.Close()
+ cfg, err := jpeg.DecodeConfig(f)
+ s.Assert(cfg.Width, qt.Equals, width)
+ s.Assert(cfg.Height, qt.Equals, height)
}
func (s *sitesBuilder) FileContent(filename string) string {
--- a/resources/image.go
+++ b/resources/image.go
@@ -24,6 +24,7 @@
"io/ioutil"
"os"
"path"
+ "path/filepath"
"strings"
"sync"
@@ -319,10 +320,13 @@
cfg := i.getSpec().imaging.Cfg
df := i.getResourcePaths().relTargetDirFile
+ if fi := i.getFileInfo(); fi != nil {
+ df.dir = filepath.Dir(fi.Meta().Path())
+ }
p1, _ := helpers.FileAndExt(df.file)
h, _ := i.hash()
idStr := internal.HashString(h, i.size(), imageMetaVersionNumber, cfg)
- return path.Join(df.dir, fmt.Sprintf("%s%s.json", p1, idStr))
+ return path.Join(df.dir, fmt.Sprintf("%s_%s.json", p1, idStr))
}
func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) dirFile {
--- a/resources/image_cache.go
+++ b/resources/image_cache.go
@@ -73,11 +73,18 @@
parent *imageResource, conf images.ImageConfig,
createImage func() (*imageResource, image.Image, error)) (*resourceAdapter, error) {
relTarget := parent.relTargetPathFromConfig(conf)
- key := parent.relTargetPathForRel(relTarget.path(), false, false, false)
+ memKey := parent.relTargetPathForRel(relTarget.path(), false, false, false)
+ // For the file cache we want to generate and store it once if possible.
+ fileKeyPath := relTarget
+ if fi := parent.root.getFileInfo(); fi != nil {
+ fileKeyPath.dir = filepath.ToSlash(filepath.Dir(fi.Meta().Path()))
+ }
+ fileKey := fileKeyPath.path()
+
// First check the in-memory store, then the disk.
c.mu.RLock()
- cachedImage, found := c.store[key]
+ cachedImage, found := c.store[memKey]
c.mu.RUnlock()
if found {
@@ -133,7 +140,7 @@
// but the count of processed image variations for this site.
c.pathSpec.ProcessingStats.Incr(&c.pathSpec.ProcessingStats.ProcessedImages)
- _, err := c.fileCache.ReadOrCreate(key, read, create)
+ _, err := c.fileCache.ReadOrCreate(fileKey, read, create)
if err != nil {
return nil, err
}
@@ -142,13 +149,13 @@
img.setSourceFs(c.fileCache.Fs)
c.mu.Lock()
- if cachedImage, found = c.store[key]; found {
+ if cachedImage, found = c.store[memKey]; found {
c.mu.Unlock()
return cachedImage, nil
}
imgAdapter := newResourceAdapter(parent.getSpec(), true, img)
- c.store[key] = imgAdapter
+ c.store[memKey] = imgAdapter
c.mu.Unlock()
return imgAdapter, nil
--- a/resources/image_test.go
+++ b/resources/image_test.go
@@ -18,6 +18,7 @@
"math/big"
"math/rand"
"os"
+ "path"
"path/filepath"
"regexp"
"strconv"
@@ -90,17 +91,16 @@
resized0x, err := image.Resize("x200")
c.Assert(err, qt.IsNil)
assertWidthHeight(resized0x, 320, 200)
- assertFileCache(c, fileCache, resized0x.RelPermalink(), 320, 200)
+ assertFileCache(c, fileCache, path.Base(resized0x.RelPermalink()), 320, 200)
resizedx0, err := image.Resize("200x")
c.Assert(err, qt.IsNil)
assertWidthHeight(resizedx0, 200, 125)
- assertFileCache(c, fileCache, resizedx0.RelPermalink(), 200, 125)
+ assertFileCache(c, fileCache, path.Base(resizedx0.RelPermalink()), 200, 125)
resizedAndRotated, err := image.Resize("x200 r90")
c.Assert(err, qt.IsNil)
assertWidthHeight(resizedAndRotated, 125, 200)
- assertFileCache(c, fileCache, resizedAndRotated.RelPermalink(), 125, 200)
assertWidthHeight(resized, 300, 200)
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_resize_q68_linear.jpg")
@@ -121,19 +121,16 @@
c.Assert(err, qt.IsNil)
c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_fill_q68_linear_bottomleft.jpg")
assertWidthHeight(filled, 200, 100)
- assertFileCache(c, fileCache, filled.RelPermalink(), 200, 100)
smart, err := image.Fill("200x100 smart")
c.Assert(err, qt.IsNil)
c.Assert(smart.RelPermalink(), qt.Equals, fmt.Sprintf("/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_fill_q68_linear_smart%d.jpg", 1))
assertWidthHeight(smart, 200, 100)
- assertFileCache(c, fileCache, smart.RelPermalink(), 200, 100)
// Check cache
filledAgain, err := image.Fill("200x100 bottomLeft")
c.Assert(err, qt.IsNil)
c.Assert(filled, eq, filledAgain)
- assertFileCache(c, fileCache, filledAgain.RelPermalink(), 200, 100)
}
// https://github.com/gohugoio/hugo/issues/4261
@@ -294,7 +291,6 @@
c := qt.New(t)
image := fetchImage(c, "sub/gohugoio2.png")
- fileCache := image.(specProvider).getSpec().FileCaches.ImageCache().Fs
c.Assert(image.MediaType(), eq, media.PNGType)
c.Assert(image.RelPermalink(), qt.Equals, "/a/sub/gohugoio2.png")
@@ -306,7 +302,6 @@
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sub/gohugoio2_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_101x101_resize_linear_2.png")
c.Assert(resized.Width(), qt.Equals, 101)
- assertFileCache(c, fileCache, resized.RelPermalink(), 101, 101)
publishedImageFilename := filepath.Clean(resized.RelPermalink())
spec := image.(specProvider).getSpec()
@@ -321,7 +316,6 @@
c.Assert(err, qt.IsNil)
c.Assert(resizedAgain.RelPermalink(), qt.Equals, "/a/sub/gohugoio2_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_101x101_resize_linear_2.png")
c.Assert(resizedAgain.Width(), qt.Equals, 101)
- assertFileCache(c, fileCache, resizedAgain.RelPermalink(), 101, 101)
assertImageFile(c, image.(specProvider).getSpec().BaseFs.PublishFs, publishedImageFilename, 101, 101)
}
@@ -529,7 +523,7 @@
return
}
- dir1 := filepath.Join(workDir, "resources/_gen/images/a")
+ dir1 := filepath.Join(workDir, "resources/_gen/images")
dir2 := filepath.FromSlash("testdata/golden")
// The two dirs above should now be the same.
--- a/resources/resource.go
+++ b/resources/resource.go
@@ -22,6 +22,8 @@
"path/filepath"
"sync"
+ "github.com/gohugoio/hugo/hugofs"
+
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/source"
@@ -172,6 +174,7 @@
getSourceFilename() string
setSourceFilename(string)
setSourceFs(afero.Fs)
+ getFileInfo() hugofs.FileMetaInfo
hash() (string, error)
size() int
}
@@ -537,7 +540,7 @@
// the path to the file on the real filesystem.
sourceFilename string
- fi os.FileInfo
+ fi hugofs.FileMetaInfo
// A hash of the source content. Is only calculated in caching situations.
h *resourceHash
@@ -553,6 +556,10 @@
return nil, err
}
return f, nil
+}
+
+func (fi *resourceFileInfo) getFileInfo() hugofs.FileMetaInfo {
+ return fi.fi
}
func (fi *resourceFileInfo) getSourceFilename() string {
--- a/resources/resource_spec.go
+++ b/resources/resource_spec.go
@@ -22,6 +22,8 @@
"path/filepath"
"strings"
+ "github.com/gohugoio/hugo/hugofs"
+
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/cache/filecache"
@@ -194,8 +196,13 @@
relTargetDirFile: dirFile{dir: fpath, file: fname},
}
+ var fim hugofs.FileMetaInfo
+ if osFileInfo != nil {
+ fim = osFileInfo.(hugofs.FileMetaInfo)
+ }
+
gfi := &resourceFileInfo{
- fi: osFileInfo,
+ fi: fim,
openReadSeekerCloser: openReadSeekerCloser,
sourceFs: sourceFs,
sourceFilename: sourceFilename,
--- a/resources/resource_transformers/htesting/testhelpers.go
+++ b/resources/resource_transformers/htesting/testhelpers.go
@@ -39,7 +39,7 @@
cfg.Set("imaging", imagingCfg)
- fs := hugofs.NewMem(cfg)
+ fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(afero.NewMemMapFs()), cfg)
s, err := helpers.NewPathSpec(fs, cfg, nil)
if err != nil {
--- a/resources/testhelpers_test.go
+++ b/resources/testhelpers_test.go
@@ -66,6 +66,8 @@
afs = afero.NewMemMapFs()
}
+ afs = hugofs.NewBaseFileDecorator(afs)
+
c := desc.c
cfg := createTestCfg()
@@ -118,7 +120,7 @@
cfg.Set("workingDir", workDir)
- fs := hugofs.NewFrom(hugofs.Os, cfg)
+ fs := hugofs.NewFrom(hugofs.NewBaseFileDecorator(hugofs.Os), cfg)
fs.Destination = &afero.MemMapFs{}
s, err := helpers.NewPathSpec(fs, cfg, nil)