github.com/olliephillips/hugo@v0.42.2/resource/image_cache.go (about)

     1  // Copyright 2017-present The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package resource
    15  
    16  import (
    17  	"fmt"
    18  	"path/filepath"
    19  	"strings"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/gohugoio/hugo/helpers"
    24  )
    25  
    26  type imageCache struct {
    27  	cacheDir string
    28  	pathSpec *helpers.PathSpec
    29  	mu       sync.RWMutex
    30  
    31  	store map[string]*Image
    32  }
    33  
    34  func (c *imageCache) isInCache(key string) bool {
    35  	c.mu.RLock()
    36  	_, found := c.store[key]
    37  	c.mu.RUnlock()
    38  	return found
    39  }
    40  
    41  func (c *imageCache) deleteByPrefix(prefix string) {
    42  	c.mu.Lock()
    43  	defer c.mu.Unlock()
    44  	for k := range c.store {
    45  		if strings.HasPrefix(k, prefix) {
    46  			delete(c.store, k)
    47  		}
    48  	}
    49  }
    50  
    51  func (c *imageCache) clear() {
    52  	c.mu.Lock()
    53  	defer c.mu.Unlock()
    54  	c.store = make(map[string]*Image)
    55  }
    56  
    57  func (c *imageCache) getOrCreate(
    58  	parent *Image, conf imageConfig, create func(resourceCacheFilename string) (*Image, error)) (*Image, error) {
    59  
    60  	relTarget := parent.relTargetPathFromConfig(conf)
    61  	key := parent.relTargetPathForRel(relTarget.path(), false)
    62  
    63  	if c.pathSpec.Language != nil {
    64  		// Avoid do and store more work than needed. The language versions will in
    65  		// most cases be duplicates of the same image files.
    66  		key = strings.TrimPrefix(key, "/"+c.pathSpec.Language.Lang)
    67  	}
    68  
    69  	// First check the in-memory store, then the disk.
    70  	c.mu.RLock()
    71  	img, found := c.store[key]
    72  	c.mu.RUnlock()
    73  
    74  	if found {
    75  		return img, nil
    76  	}
    77  
    78  	// Now look in the file cache.
    79  	// Multiple Go routines can invoke same operation on the same image, so
    80  	// we need to make sure this is serialized per source image.
    81  	parent.createMu.Lock()
    82  	defer parent.createMu.Unlock()
    83  
    84  	cacheFilename := filepath.Join(c.cacheDir, key)
    85  
    86  	// The definition of this counter is not that we have processed that amount
    87  	// (e.g. resized etc.), it can be fetched from file cache,
    88  	//  but the count of processed image variations for this site.
    89  	c.pathSpec.ProcessingStats.Incr(&c.pathSpec.ProcessingStats.ProcessedImages)
    90  
    91  	exists, err := helpers.Exists(cacheFilename, c.pathSpec.BaseFs.ResourcesFs)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	if exists {
    97  		img = parent.clone()
    98  		img.relTargetPath.file = relTarget.file
    99  		img.sourceFilename = cacheFilename
   100  		// We have to look resources file system for this.
   101  		img.overriddenSourceFs = img.spec.BaseFs.ResourcesFs
   102  	} else {
   103  		img, err = create(cacheFilename)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  	}
   108  
   109  	c.mu.Lock()
   110  	if img2, found := c.store[key]; found {
   111  		c.mu.Unlock()
   112  		return img2, nil
   113  	}
   114  
   115  	c.store[key] = img
   116  
   117  	c.mu.Unlock()
   118  
   119  	if !exists {
   120  		// File already written to destination
   121  		return img, nil
   122  	}
   123  
   124  	return img, img.copyToDestination(cacheFilename)
   125  
   126  }
   127  
   128  func newImageCache(ps *helpers.PathSpec, cacheDir string) *imageCache {
   129  	return &imageCache{pathSpec: ps, store: make(map[string]*Image), cacheDir: cacheDir}
   130  }
   131  
   132  func timeTrack(start time.Time, name string) {
   133  	elapsed := time.Since(start)
   134  	fmt.Printf("%s took %s\n", name, elapsed)
   135  }