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 }