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 }