From fbb8eb39ecda4151e1f50355688071c67f8bbcf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sun, 30 Jul 2023 11:26:29 +0200 Subject: [PATCH] Fix so temporary images do not get published Fixes #10255 --- hugolib/integrationtest_builder.go | 18 ++++++++++ resources/image_cache.go | 22 ++----------- resources/integration_test.go | 39 ++++++++++++++++++++++ resources/resource.go | 53 ++++++++++++++---------------- 4 files changed, 83 insertions(+), 49 deletions(-) diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go index 6a5cd1ce332..c06c9d25f8c 100644 --- a/hugolib/integrationtest_builder.go +++ b/hugolib/integrationtest_builder.go @@ -137,6 +137,24 @@ func (s *IntegrationTestBuilder) AssertBuildCountTranslations(count int) { s.Assert(s.H.init.translations.InitCount(), qt.Equals, count) } +func (s *IntegrationTestBuilder) AssertFileCount(dirname string, expected int) { + s.Helper() + fs := s.fs.WorkingDirReadOnly + count := 0 + afero.Walk(fs, dirname, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + count++ + return nil + }) + s.Assert(count, qt.Equals, expected) + +} + func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...string) { s.Helper() content := strings.TrimSpace(s.FileContent(filename)) diff --git a/resources/image_cache.go b/resources/image_cache.go index 636607f9452..542a6836fc1 100644 --- a/resources/image_cache.go +++ b/resources/image_cache.go @@ -106,28 +106,10 @@ func (c *ImageCache) getOrCreate( rp := img.getResourcePaths() rp.relTargetDirFile.file = relTarget.file img.setSourceFilename(info.Name) + img.setSourfeFilenameIsHash(true) img.setMediaType(conf.TargetFormat.MediaType()) - if err := img.InitConfig(r); err != nil { - return err - } - - r.Seek(0, 0) - - w, err := img.openDestinationsForWriting() - if err != nil { - return err - } - - if w == nil { - // Nothing to write. - return nil - } - - defer w.Close() - _, err = io.Copy(w, r) - - return err + return img.InitConfig(r) } // create creates the image and encodes it to the cache (w). diff --git a/resources/integration_test.go b/resources/integration_test.go index 543c264dcf1..2075079dcc2 100644 --- a/resources/integration_test.go +++ b/resources/integration_test.go @@ -100,3 +100,42 @@ Width: {{ $svg.Width }} b.Assert(err.Error(), qt.Contains, `error calling Width: this method is only available for raster images. To determine if an image is SVG, you can do {{ if eq .MediaType.SubType "svg" }}{{ end }}`) } + +// Issue 10255. +func TestNoPublishOfUnusedProcessedImage(t *testing.T) { + t.Parallel() + + workingDir := t.TempDir() + + files := ` +-- assets/images/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{ $image := resources.Get "images/pixel.png" }} +{{ $image = $image.Resize "400x" }} +{{ $image = $image.Resize "300x" }} +{{ $image = $image.Resize "200x" }} +{{ $image = $image.Resize "100x" }} +{{ $image = $image.Crop "50x50" }} +{{ $image = $image.Filter (images.GaussianBlur 6) }} +{{ ($image | fingerprint).Permalink }} + + +` + + for i := 0; i < 3; i++ { + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: true, + WorkingDir: workingDir, + }).Build() + + b.AssertFileCount("resources/_gen/images", 6) + b.AssertFileCount("public/images", 1) + b.Build() + } + +} diff --git a/resources/resource.go b/resources/resource.go index d1acc102681..884900d5870 100644 --- a/resources/resource.go +++ b/resources/resource.go @@ -161,7 +161,6 @@ type baseResourceInternal interface { specProvider getResourcePaths() *resourcePathDescriptor getTargetFilenames() []string - openDestinationsForWriting() (io.WriteCloser, error) openPublishFileForWriting(relTargetPath string) (io.WriteCloser, error) relTargetPathForRel(rel string, addBaseTargetPath, isAbs, isURL bool) string @@ -216,6 +215,7 @@ func (d dirFile) path() string { type fileInfo interface { getSourceFilename() string setSourceFilename(string) + setSourfeFilenameIsHash(bool) setSourceFs(afero.Fs) getFileInfo() hugofs.FileMetaInfo hash() (string, error) @@ -304,6 +304,21 @@ func (l *genericResource) Permalink() string { func (l *genericResource) Publish() error { var err error l.publishInit.Do(func() { + targetFilenames := l.getTargetFilenames() + if l.sourceFilenameIsHash { + // This is a processed image. We want to avoid copying it if it hasn't changed. + var changedFilenames []string + for _, targetFilename := range targetFilenames { + if _, err := l.getSpec().BaseFs.PublishFs.Stat(targetFilename); err == nil { + continue + } + changedFilenames = append(changedFilenames, targetFilename) + } + if len(changedFilenames) == 0 { + return + } + targetFilenames = changedFilenames + } var fr hugio.ReadSeekCloser fr, err = l.ReadSeekCloser() if err != nil { @@ -312,7 +327,7 @@ func (l *genericResource) Publish() error { defer fr.Close() var fw io.WriteCloser - fw, err = helpers.OpenFilesForWriting(l.spec.BaseFs.PublishFs, l.getTargetFilenames()...) + fw, err = helpers.OpenFilesForWriting(l.spec.BaseFs.PublishFs, targetFilenames...) if err != nil { return } @@ -475,33 +490,6 @@ func (l genericResource) clone() *genericResource { return &l } -// returns an opened file or nil if nothing to write (it may already be published). -func (l *genericResource) openDestinationsForWriting() (w io.WriteCloser, err error) { - l.publishInit.Do(func() { - targetFilenames := l.getTargetFilenames() - var changedFilenames []string - - // Fast path: - // This is a processed version of the original; - // check if it already exists at the destination. - for _, targetFilename := range targetFilenames { - if _, err := l.getSpec().BaseFs.PublishFs.Stat(targetFilename); err == nil { - continue - } - - changedFilenames = append(changedFilenames, targetFilename) - } - - if len(changedFilenames) == 0 { - return - } - - w, err = helpers.OpenFilesForWriting(l.getSpec().BaseFs.PublishFs, changedFilenames...) - }) - - return -} - func (r *genericResource) openPublishFileForWriting(relTargetPath string) (io.WriteCloser, error) { return helpers.OpenFilesForWriting(r.spec.BaseFs.PublishFs, r.relTargetPathsFor(relTargetPath)...) } @@ -622,6 +610,9 @@ type resourceFileInfo struct { // the path to the file on the real filesystem. sourceFilename string + // For performance. This means that whenever the content changes, the filename changes. + sourceFilenameIsHash bool + fi hugofs.FileMetaInfo // A hash of the source content. Is only calculated in caching situations. @@ -654,6 +645,10 @@ func (fi *resourceFileInfo) setSourceFilename(s string) { fi.sourceFilename = s } +func (fi *resourceFileInfo) setSourfeFilenameIsHash(b bool) { + fi.sourceFilenameIsHash = b +} + func (fi *resourceFileInfo) getSourceFs() afero.Fs { return fi.sourceFs }