github.com/fighterlyt/hugo@v0.47.1/resource/image_cache.go (about)

     1  // Copyright 2018 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[c.normalizeKey(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  	prefix = c.normalizeKey(prefix)
    45  	for k := range c.store {
    46  		if strings.HasPrefix(k, prefix) {
    47  			delete(c.store, k)
    48  		}
    49  	}
    50  }
    51  
    52  func (c *imageCache) normalizeKey(key string) string {
    53  	// It is a path with Unix style slashes and it always starts with a leading slash.
    54  	key = filepath.ToSlash(key)
    55  	if !strings.HasPrefix(key, "/") {
    56  		key = "/" + key
    57  	}
    58  
    59  	return key
    60  }
    61  
    62  func (c *imageCache) clear() {
    63  	c.mu.Lock()
    64  	defer c.mu.Unlock()
    65  	c.store = make(map[string]*Image)
    66  }
    67  
    68  func (c *imageCache) getOrCreate(
    69  	parent *Image, conf imageConfig, create func(resourceCacheFilename string) (*Image, error)) (*Image, error) {
    70  
    71  	relTarget := parent.relTargetPathFromConfig(conf)
    72  	key := parent.relTargetPathForRel(relTarget.path(), false, false)
    73  
    74  	// First check the in-memory store, then the disk.
    75  	c.mu.RLock()
    76  	img, found := c.store[key]
    77  	c.mu.RUnlock()
    78  
    79  	if found {
    80  		return img, nil
    81  	}
    82  
    83  	// Now look in the file cache.
    84  	// Multiple Go routines can invoke same operation on the same image, so
    85  	// we need to make sure this is serialized per source image.
    86  	parent.createMu.Lock()
    87  	defer parent.createMu.Unlock()
    88  
    89  	cacheFilename := filepath.Join(c.cacheDir, key)
    90  
    91  	// The definition of this counter is not that we have processed that amount
    92  	// (e.g. resized etc.), it can be fetched from file cache,
    93  	//  but the count of processed image variations for this site.
    94  	c.pathSpec.ProcessingStats.Incr(&c.pathSpec.ProcessingStats.ProcessedImages)
    95  
    96  	exists, err := helpers.Exists(cacheFilename, c.pathSpec.BaseFs.Resources.Fs)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	if exists {
   102  		img = parent.clone()
   103  	} else {
   104  		img, err = create(cacheFilename)
   105  		if err != nil {
   106  			return nil, err
   107  		}
   108  	}
   109  	img.relTargetDirFile.file = relTarget.file
   110  	img.sourceFilename = cacheFilename
   111  	// We have to look in the resources file system for this.
   112  	img.overriddenSourceFs = img.spec.BaseFs.Resources.Fs
   113  
   114  	c.mu.Lock()
   115  	if img2, found := c.store[key]; found {
   116  		c.mu.Unlock()
   117  		return img2, nil
   118  	}
   119  
   120  	c.store[key] = img
   121  
   122  	c.mu.Unlock()
   123  
   124  	if !exists {
   125  		// File already written to destination
   126  		return img, nil
   127  	}
   128  
   129  	return img, img.copyToDestination(cacheFilename)
   130  
   131  }
   132  
   133  func newImageCache(ps *helpers.PathSpec, cacheDir string) *imageCache {
   134  	return &imageCache{pathSpec: ps, store: make(map[string]*Image), cacheDir: cacheDir}
   135  }
   136  
   137  func timeTrack(start time.Time, name string) {
   138  	elapsed := time.Since(start)
   139  	fmt.Printf("%s took %s\n", name, elapsed)
   140  }