github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/resources/image_cache.go (about) 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 99 if err := img.InitConfig(r); err != nil { 100 return err 101 } 102 103 r.Seek(0, 0) 104 105 w, err := img.openDestinationsForWriting() 106 if err != nil { 107 return err 108 } 109 110 if w == nil { 111 // Nothing to write. 112 return nil 113 } 114 115 defer w.Close() 116 _, err = io.Copy(w, r) 117 118 return err 119 } 120 121 // create creates the image and encodes it to the cache (w). 122 create := func(info filecache.ItemInfo, w io.WriteCloser) (err error) { 123 defer w.Close() 124 125 var conv image.Image 126 img, conv, err = createImage() 127 if err != nil { 128 return 129 } 130 rp := img.getResourcePaths() 131 rp.relTargetDirFile.file = relTarget.file 132 img.setSourceFilename(info.Name) 133 134 return img.EncodeTo(conf, conv, w) 135 } 136 137 // Now look in the file cache. 138 139 // The definition of this counter is not that we have processed that amount 140 // (e.g. resized etc.), it can be fetched from file cache, 141 // but the count of processed image variations for this site. 142 c.pathSpec.ProcessingStats.Incr(&c.pathSpec.ProcessingStats.ProcessedImages) 143 144 _, err := c.fileCache.ReadOrCreate(fileKey, read, create) 145 if err != nil { 146 return nil, err 147 } 148 149 // The file is now stored in this cache. 150 img.setSourceFs(c.fileCache.Fs) 151 152 c.mu.Lock() 153 if cachedImage, found = c.store[memKey]; found { 154 c.mu.Unlock() 155 return cachedImage, nil 156 } 157 158 imgAdapter := newResourceAdapter(parent.getSpec(), true, img) 159 c.store[memKey] = imgAdapter 160 c.mu.Unlock() 161 162 return imgAdapter, nil 163 } 164 165 func newImageCache(fileCache *filecache.Cache, ps *helpers.PathSpec) *imageCache { 166 return &imageCache{fileCache: fileCache, pathSpec: ps, store: make(map[string]*resourceAdapter)} 167 }