hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

image_cache.go (4352B)

    1 // Copyright 2019 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 resources
   15 
   16 import (
   17 	"image"
   18 	"io"
   19 	"path/filepath"
   20 	"strings"
   21 	"sync"
   22 
   23 	"github.com/gohugoio/hugo/resources/images"
   24 
   25 	"github.com/gohugoio/hugo/cache/filecache"
   26 	"github.com/gohugoio/hugo/helpers"
   27 )
   28 
   29 type imageCache struct {
   30 	pathSpec *helpers.PathSpec
   31 
   32 	fileCache *filecache.Cache
   33 
   34 	mu    sync.RWMutex
   35 	store map[string]*resourceAdapter
   36 }
   37 
   38 func (c *imageCache) deleteIfContains(s string) {
   39 	c.mu.Lock()
   40 	defer c.mu.Unlock()
   41 	s = c.normalizeKeyBase(s)
   42 	for k := range c.store {
   43 		if strings.Contains(k, s) {
   44 			delete(c.store, k)
   45 		}
   46 	}
   47 }
   48 
   49 // The cache key is a lowercase path with Unix style slashes and it always starts with
   50 // a leading slash.
   51 func (c *imageCache) normalizeKey(key string) string {
   52 	return "/" + c.normalizeKeyBase(key)
   53 }
   54 
   55 func (c *imageCache) normalizeKeyBase(key string) string {
   56 	return strings.Trim(strings.ToLower(filepath.ToSlash(key)), "/")
   57 }
   58 
   59 func (c *imageCache) clear() {
   60 	c.mu.Lock()
   61 	defer c.mu.Unlock()
   62 	c.store = make(map[string]*resourceAdapter)
   63 }
   64 
   65 func (c *imageCache) getOrCreate(
   66 	parent *imageResource, conf images.ImageConfig,
   67 	createImage func() (*imageResource, image.Image, error)) (*resourceAdapter, error) {
   68 	relTarget := parent.relTargetPathFromConfig(conf)
   69 	memKey := parent.relTargetPathForRel(relTarget.path(), false, false, false)
   70 	memKey = c.normalizeKey(memKey)
   71 
   72 	// For the file cache we want to generate and store it once if possible.
   73 	fileKeyPath := relTarget
   74 	if fi := parent.root.getFileInfo(); fi != nil {
   75 		fileKeyPath.dir = filepath.ToSlash(filepath.Dir(fi.Meta().Path))
   76 	}
   77 	fileKey := fileKeyPath.path()
   78 
   79 	// First check the in-memory store, then the disk.
   80 	c.mu.RLock()
   81 	cachedImage, found := c.store[memKey]
   82 	c.mu.RUnlock()
   83 
   84 	if found {
   85 		return cachedImage, nil
   86 	}
   87 
   88 	var img *imageResource
   89 
   90 	// These funcs are protected by a named lock.
   91 	// read clones the parent to its new name and copies
   92 	// the content to the destinations.
   93 	read := func(info filecache.ItemInfo, r io.ReadSeeker) error {
   94 		img = parent.clone(nil)
   95 		rp := img.getResourcePaths()
   96 		rp.relTargetDirFile.file = relTarget.file
   97 		img.setSourceFilename(info.Name)
   98 		img.setMediaType(conf.TargetFormat.MediaType())
   99 
  100 		if err := img.InitConfig(r); err != nil {
  101 			return err
  102 		}
  103 
  104 		r.Seek(0, 0)
  105 
  106 		w, err := img.openDestinationsForWriting()
  107 		if err != nil {
  108 			return err
  109 		}
  110 
  111 		if w == nil {
  112 			// Nothing to write.
  113 			return nil
  114 		}
  115 
  116 		defer w.Close()
  117 		_, err = io.Copy(w, r)
  118 
  119 		return err
  120 	}
  121 
  122 	// create creates the image and encodes it to the cache (w).
  123 	create := func(info filecache.ItemInfo, w io.WriteCloser) (err error) {
  124 		defer w.Close()
  125 
  126 		var conv image.Image
  127 		img, conv, err = createImage()
  128 		if err != nil {
  129 			return
  130 		}
  131 		rp := img.getResourcePaths()
  132 		rp.relTargetDirFile.file = relTarget.file
  133 		img.setSourceFilename(info.Name)
  134 
  135 		return img.EncodeTo(conf, conv, w)
  136 	}
  137 
  138 	// Now look in the file cache.
  139 
  140 	// The definition of this counter is not that we have processed that amount
  141 	// (e.g. resized etc.), it can be fetched from file cache,
  142 	//  but the count of processed image variations for this site.
  143 	c.pathSpec.ProcessingStats.Incr(&c.pathSpec.ProcessingStats.ProcessedImages)
  144 
  145 	_, err := c.fileCache.ReadOrCreate(fileKey, read, create)
  146 	if err != nil {
  147 		return nil, err
  148 	}
  149 
  150 	// The file is now stored in this cache.
  151 	img.setSourceFs(c.fileCache.Fs)
  152 
  153 	c.mu.Lock()
  154 	if cachedImage, found = c.store[memKey]; found {
  155 		c.mu.Unlock()
  156 		return cachedImage, nil
  157 	}
  158 
  159 	imgAdapter := newResourceAdapter(parent.getSpec(), true, img)
  160 	c.store[memKey] = imgAdapter
  161 	c.mu.Unlock()
  162 
  163 	return imgAdapter, nil
  164 }
  165 
  166 func newImageCache(fileCache *filecache.Cache, ps *helpers.PathSpec) *imageCache {
  167 	return &imageCache{fileCache: fileCache, pathSpec: ps, store: make(map[string]*resourceAdapter)}
  168 }