github.com/puellanivis/breton@v0.2.16/lib/files/cachefiles/cache.go (about) 1 // Package cachefiles implements a caching filestore accessable through "cache:opaqueURL". 2 package cachefiles 3 4 import ( 5 "bytes" 6 "context" 7 "net/url" 8 "os" 9 "sync" 10 "time" 11 12 "github.com/puellanivis/breton/lib/files" 13 "github.com/puellanivis/breton/lib/files/wrapper" 14 ) 15 16 type line struct { 17 info os.FileInfo 18 data []byte 19 } 20 21 // FileStore is a caching structure that holds copies of the content of files. 22 type FileStore struct { 23 mu sync.RWMutex 24 25 cache map[string]*line 26 } 27 28 // New returns a new caching FileStore, which can be registered into lib/files 29 // 30 // Deprecated: Now that we use sensible defaults, and lazy initialization, 31 // a simple &cachefiles.FileStore{} or new(cachefiles.FileStore) is enough now. 32 func New() *FileStore { 33 return &FileStore{} 34 } 35 36 // Default is the default cache attached to the "cache" Scheme 37 var Default = new(FileStore) 38 39 func init() { 40 files.RegisterScheme(Default, "cache") 41 } 42 43 func (h *FileStore) expire(filename string) { 44 h.mu.Lock() 45 defer h.mu.Unlock() 46 47 delete(h.cache, filename) 48 } 49 50 func trimScheme(uri *url.URL) string { 51 u := *uri 52 u.Scheme = "" 53 54 return u.String() 55 } 56 57 // Open implements the files.FileStore Open. It returns a buffered copy of the files.Reader returned from reading the uri escaped by the "cache:" scheme. Any access within the next ExpireTime set by the context.Context (5 minutes by default) will return a new copy of an bytes.Reader of the same buffer. 58 func (h *FileStore) Open(ctx context.Context, uri *url.URL) (files.Reader, error) { 59 filename := trimScheme(uri) 60 61 ctx, reentrant := isReentrant(ctx) 62 if reentrant { 63 // We are in a reentrant caching scenario. 64 // Continuing will deadlock, so we won’t even try to cache at all. 65 return files.Open(ctx, filename) 66 } 67 68 h.mu.RLock() 69 f := h.cache[filename] 70 h.mu.RUnlock() 71 72 if f != nil { 73 return wrapper.NewReaderWithInfo(bytes.NewReader(f.data), f.info), nil 74 } 75 76 h.mu.Lock() 77 defer h.mu.Unlock() 78 79 f = h.cache[filename] 80 if f != nil { 81 // Another goroutine already did our work. 82 return wrapper.NewReaderWithInfo(bytes.NewReader(f.data), f.info), nil 83 } 84 85 raw, err := files.Open(ctx, filename) 86 if err != nil { 87 return nil, err 88 } 89 90 info, err := raw.Stat() 91 if err != nil { 92 // Just in case, if we got an err != nil return, 93 // we want to be absolutely sure we don’t try and use the returned `info`. 94 // Instead, we will make up our own. 95 info = nil 96 } 97 98 data, err := files.ReadFrom(raw) 99 if err != nil { 100 return nil, err 101 } 102 103 if info == nil { 104 info = wrapper.NewInfo(uri, len(data), time.Now()) 105 } 106 107 f = &line{ 108 data: data, 109 info: info, 110 } 111 112 if h.cache == nil { 113 h.cache = make(map[string]*line) 114 } 115 116 h.cache[filename] = f 117 118 // default 5 minute expiration 119 expiration := 5 * time.Minute 120 if d, ok := GetExpire(ctx); ok { 121 expiration = d 122 } 123 timer := time.NewTimer(expiration) 124 125 go func() { 126 <-timer.C 127 h.expire(filename) 128 }() 129 130 return wrapper.NewReaderWithInfo(bytes.NewReader(data), info), nil 131 } 132 133 // Create implements the files.FileStore Create. At this time, it just returns the files.Create() from the wrapped url. 134 func (h *FileStore) Create(ctx context.Context, uri *url.URL) (files.Writer, error) { 135 return files.Create(ctx, trimScheme(uri)) 136 } 137 138 // List implements the files.FileStore List. It does not cache anything and just returns the files.List() from the wrapped url. 139 func (h *FileStore) List(ctx context.Context, uri *url.URL) ([]os.FileInfo, error) { 140 return files.List(ctx, trimScheme(uri)) 141 }