diff --git a/deps/deps.go b/deps/deps.go index 0eeb4248b..39462de96 100644 --- a/deps/deps.go +++ b/deps/deps.go @@ -189,10 +189,13 @@ func (d *Deps) Init() error { } var common *resources.SpecCommon + var imageCache *resources.ImageCache if d.ResourceSpec != nil { common = d.ResourceSpec.SpecCommon + imageCache = d.ResourceSpec.ImageCache } - resourceSpec, err := resources.NewSpec(d.PathSpec, common, d.BuildState, d.Log, d, d.ExecHelper) + + resourceSpec, err := resources.NewSpec(d.PathSpec, common, imageCache, d.BuildState, d.Log, d, d.ExecHelper) if err != nil { return fmt.Errorf("failed to create resource spec: %w", err) } diff --git a/main_test.go b/main_test.go index 73b9a0838..5629db3fe 100644 --- a/main_test.go +++ b/main_test.go @@ -370,12 +370,15 @@ var commonTestScriptsParam = testscript.Params{ } func testSetupFunc() func(env *testscript.Env) error { + sourceDir, _ := os.Getwd() return func(env *testscript.Env) error { var keyVals []string keyVals = append(keyVals, "HUGO_TESTRUN", "true") hugoCachedDir := filepath.Join(env.WorkDir, "hugocache") keyVals = append(keyVals, "HUGO_CACHEDIR", hugoCachedDir) + keyVals = append(keyVals, "SOURCE", sourceDir) + goVersion := runtime.Version() // Strip all but the major and minor version. goVersion = regexp.MustCompile(`^go(\d+\.\d+)`).FindStringSubmatch(goVersion)[1] diff --git a/resources/image.go b/resources/image.go index c61e903ab..ad2f9de32 100644 --- a/resources/image.go +++ b/resources/image.go @@ -126,7 +126,7 @@ func (i *imageResource) getExif() *exif.ExifInfo { return enc.Encode(i.meta) } - _, i.metaInitErr = i.getSpec().imageCache.fileCache.ReadOrCreate(key, read, create) + _, i.metaInitErr = i.getSpec().ImageCache.fileCache.ReadOrCreate(key, read, create) }) if i.metaInitErr != nil { @@ -296,7 +296,7 @@ const imageProcWorkers = 1 var imageProcSem = make(chan bool, imageProcWorkers) func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src image.Image) (image.Image, error)) (images.ImageResource, error) { - img, err := i.getSpec().imageCache.getOrCreate(i, conf, func() (*imageResource, image.Image, error) { + img, err := i.getSpec().ImageCache.getOrCreate(i, conf, func() (*imageResource, image.Image, error) { imageProcSem <- true defer func() { <-imageProcSem diff --git a/resources/image_cache.go b/resources/image_cache.go index ca651fd5c..636607f94 100644 --- a/resources/image_cache.go +++ b/resources/image_cache.go @@ -26,16 +26,27 @@ import ( "github.com/gohugoio/hugo/helpers" ) -type imageCache struct { +// ImageCache is a cache for image resources. The backing caches are shared between all sites. +type ImageCache struct { pathSpec *helpers.PathSpec fileCache *filecache.Cache + *imageCacheStore +} + +type imageCacheStore struct { mu sync.RWMutex store map[string]*resourceAdapter } -func (c *imageCache) deleteIfContains(s string) { +// WithPathSpec returns a copy of the ImageCache with the given PathSpec set. +func (c ImageCache) WithPathSpec(ps *helpers.PathSpec) *ImageCache { + c.pathSpec = ps + return &c +} + +func (c *ImageCache) deleteIfContains(s string) { c.mu.Lock() defer c.mu.Unlock() s = c.normalizeKeyBase(s) @@ -48,21 +59,21 @@ func (c *imageCache) deleteIfContains(s string) { // The cache key is a lowercase path with Unix style slashes and it always starts with // a leading slash. -func (c *imageCache) normalizeKey(key string) string { +func (c *ImageCache) normalizeKey(key string) string { return "/" + c.normalizeKeyBase(key) } -func (c *imageCache) normalizeKeyBase(key string) string { +func (c *ImageCache) normalizeKeyBase(key string) string { return strings.Trim(strings.ToLower(filepath.ToSlash(key)), "/") } -func (c *imageCache) clear() { +func (c *ImageCache) clear() { c.mu.Lock() defer c.mu.Unlock() c.store = make(map[string]*resourceAdapter) } -func (c *imageCache) getOrCreate( +func (c *ImageCache) getOrCreate( parent *imageResource, conf images.ImageConfig, createImage func() (*imageResource, image.Image, error)) (*resourceAdapter, error) { relTarget := parent.relTargetPathFromConfig(conf) @@ -163,6 +174,6 @@ func (c *imageCache) getOrCreate( return imgAdapter, nil } -func newImageCache(fileCache *filecache.Cache, ps *helpers.PathSpec) *imageCache { - return &imageCache{fileCache: fileCache, pathSpec: ps, store: make(map[string]*resourceAdapter)} +func newImageCache(fileCache *filecache.Cache, ps *helpers.PathSpec) *ImageCache { + return &ImageCache{fileCache: fileCache, pathSpec: ps, imageCacheStore: &imageCacheStore{store: make(map[string]*resourceAdapter)}} } diff --git a/resources/resource_spec.go b/resources/resource_spec.go index 4d2ceccb3..5ecb021fe 100644 --- a/resources/resource_spec.go +++ b/resources/resource_spec.go @@ -51,6 +51,7 @@ import ( func NewSpec( s *helpers.PathSpec, common *SpecCommon, // may be nil + imageCache *ImageCache, // may be nil incr identity.Incrementer, logger loggers.Logger, errorHandler herrors.ErrorSender, @@ -90,11 +91,6 @@ func NewSpec( PostProcessResources: make(map[string]postpub.PostPublishedResource), JSConfigBuilder: jsconfig.NewBuilder(), }, - imageCache: newImageCache( - fileCaches.ImageCache(), - - s, - ), ResourceCache: &ResourceCache{ fileCache: fileCaches.AssetsCache(), cache: make(map[string]any), @@ -103,11 +99,22 @@ func NewSpec( } } + if imageCache == nil { + imageCache = newImageCache( + fileCaches.ImageCache(), + s, + ) + } else { + imageCache = imageCache.WithPathSpec(s) + + } + rs := &Spec{ PathSpec: s, Logger: logger, ErrorSender: errorHandler, imaging: imaging, + ImageCache: imageCache, ExecHelper: execHelper, Permalinks: permalinks, @@ -128,6 +135,8 @@ type Spec struct { Permalinks page.PermalinkExpander + ImageCache *ImageCache + // Holds default filter settings etc. imaging *images.ImageProcessor @@ -139,7 +148,6 @@ type Spec struct { // The parts of Spec that's comoon for all sites. type SpecCommon struct { incr identity.Incrementer - imageCache *imageCache ResourceCache *ResourceCache FileCaches filecache.Caches @@ -171,13 +179,13 @@ func (r *Spec) BuildConfig() config.BuildConfig { } func (r *Spec) CacheStats() string { - r.imageCache.mu.RLock() - defer r.imageCache.mu.RUnlock() + r.ImageCache.mu.RLock() + defer r.ImageCache.mu.RUnlock() - s := fmt.Sprintf("Cache entries: %d", len(r.imageCache.store)) + s := fmt.Sprintf("Cache entries: %d", len(r.ImageCache.store)) count := 0 - for k := range r.imageCache.store { + for k := range r.ImageCache.store { if count > 5 { break } @@ -189,12 +197,12 @@ func (r *Spec) CacheStats() string { } func (r *Spec) ClearCaches() { - r.imageCache.clear() + r.ImageCache.clear() r.ResourceCache.clear() } func (r *Spec) DeleteBySubstring(s string) { - r.imageCache.deleteIfContains(s) + r.ImageCache.deleteIfContains(s) } func (s *Spec) String() string { diff --git a/resources/resource_transformers/htesting/testhelpers.go b/resources/resource_transformers/htesting/testhelpers.go index 75ae4245e..b1feccc5f 100644 --- a/resources/resource_transformers/htesting/testhelpers.go +++ b/resources/resource_transformers/htesting/testhelpers.go @@ -43,7 +43,7 @@ func NewTestResourceSpec() (*resources.Spec, error) { return nil, err } - spec, err := resources.NewSpec(s, nil, nil, nil, nil, nil) + spec, err := resources.NewSpec(s, nil, nil, nil, nil, nil, nil) return spec, err } diff --git a/resources/testdata/pix.gif b/resources/testdata/pix.gif new file mode 100644 index 000000000..f191b280c Binary files /dev/null and b/resources/testdata/pix.gif differ diff --git a/testscripts/commands/hugo__processingstats.txt b/testscripts/commands/hugo__processingstats.txt new file mode 100644 index 000000000..0e700b607 --- /dev/null +++ b/testscripts/commands/hugo__processingstats.txt @@ -0,0 +1,34 @@ +cp $SOURCE/resources/testdata/pix.gif content/en/bundle1/pix.gif +cp $SOURCE/resources/testdata/pix.gif content/en/bundle2/pix.gif +cp $SOURCE/resources/testdata/pix.gif content/fr/bundle1/pix.gif + +hugo + +stdout 'Pages.*3.*2' +stdout 'Processed images.*2.*1' + +-- content/en/bundle1/index.md -- +-- content/en/bundle2/index.md -- +-- content/fr/bundle1/index.md -- +-- hugo.toml -- +disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404"] +baseURL = "https://example.com/" +[languages] + [languages.en] + languageName = "English" + weight = 1 + title = "English Title" + contentDir = "content/en" + [languages.fr] + languageName = "French" + weight = 2 + title = "French Title" + contentDir = "content/fr" +-- layouts/index.html -- +Home. +-- layouts/_default/single.html -- +Single. +{{ range .Resources }} +{{ $img := .Resize "3x" }} +Resized: {{ $img.RelPermalink }} +{{ end }}