1 package storage
2
3 import (
4 "context"
5 "io"
6 "time"
7 )
8
9 type CacheOptions struct {
10
11
12
13 MaxAge time.Duration
14
15
16
17
18 DefaultExpired bool
19
20
21 NoData bool
22 }
23
24
25 func NewCacheWrapper(src, cache FS, options *CacheOptions) FS {
26 if options == nil {
27 options = &CacheOptions{}
28 }
29
30 return &cacheWrapper{
31 src: src,
32 cache: cache,
33 options: options,
34 }
35 }
36
37 type openForwarder struct {
38 src func() (io.ReadCloser, error)
39 rc io.ReadCloser
40 }
41
42 func (f *openForwarder) open() error {
43 if f.rc != nil {
44 return nil
45 }
46
47 rc, err := f.src()
48 if err != nil {
49 return err
50 }
51 f.rc = rc
52
53 return nil
54 }
55
56 func (f *openForwarder) Read(p []byte) (n int, err error) {
57 if err = f.open(); err != nil {
58 return 0, err
59 }
60
61 return f.rc.Read(p)
62 }
63
64 func (f *openForwarder) Close() (err error) {
65 if f.rc == nil {
66 return nil
67 }
68
69 return f.rc.Close()
70 }
71
72 type cacheWrapper struct {
73 src FS
74 cache FS
75 options *CacheOptions
76 }
77
78 func (c *cacheWrapper) isExpired(file *File) bool {
79 if c.options.MaxAge == 0 {
80
81 return false
82 }
83
84 creationTime := file.CreationTime
85 if creationTime.IsZero() {
86 creationTime = file.ModTime
87 }
88 if creationTime.IsZero() {
89 return c.options.DefaultExpired
90 }
91
92 return time.Since(creationTime) > c.options.MaxAge
93 }
94
95 func (c *cacheWrapper) openCache(ctx context.Context, path string, options *ReaderOptions) (*File, error) {
96 f, err := c.cache.Open(ctx, path, options)
97 if err != nil {
98 return nil, err
99 }
100 if c.options.NoData {
101
102
103 f.ReadCloser = &openForwarder{
104 src: func() (io.ReadCloser, error) {
105 return c.src.Open(ctx, path, options)
106 },
107 }
108 }
109
110 return f, nil
111 }
112
113
114 func (c *cacheWrapper) Open(ctx context.Context, path string, options *ReaderOptions) (*File, error) {
115 f, err := c.openCache(ctx, path, options)
116 if err == nil {
117 if !c.isExpired(f) {
118 return f, nil
119 }
120 } else if !IsNotExist(err) {
121 return nil, err
122 }
123
124 sf, err := c.src.Open(ctx, path, options)
125 if err != nil {
126 return nil, err
127 }
128 defer sf.Close()
129
130 cacheAttrs := sf.Attributes
131 cacheAttrs.CreationTime = time.Now()
132 wc, err := c.cache.Create(ctx, path, &WriterOptions{
133 Attributes: cacheAttrs,
134 })
135 if err != nil {
136 return nil, err
137 }
138
139 if !c.options.NoData {
140 if _, err := io.Copy(wc, sf); err != nil {
141 wc.Close()
142
143 return nil, err
144 }
145 }
146
147 if err := wc.Close(); err != nil {
148 return nil, err
149 }
150
151 ff, err := c.openCache(ctx, path, options)
152 if err != nil {
153 return nil, err
154 }
155
156 return ff, nil
157 }
158
159
160 func (c *cacheWrapper) Attributes(ctx context.Context, path string, options *ReaderOptions) (*Attributes, error) {
161 f, err := c.Open(ctx, path, options)
162 if err != nil {
163 return nil, err
164 }
165
166 return &f.Attributes, nil
167 }
168
169
170 func (c *cacheWrapper) Delete(ctx context.Context, path string) error {
171 err := c.cache.Delete(ctx, path)
172 if err != nil && !IsNotExist(err) {
173 return err
174 }
175
176 return c.src.Delete(ctx, path)
177 }
178
179
180 func (c *cacheWrapper) Create(ctx context.Context, path string, options *WriterOptions) (io.WriteCloser, error) {
181 err := c.cache.Delete(ctx, path)
182 if err != nil && !IsNotExist(err) {
183 return nil, err
184 }
185
186 return c.src.Create(ctx, path, options)
187 }
188
189
190 func (c *cacheWrapper) Walk(ctx context.Context, path string, fn WalkFn) error {
191 return c.src.Walk(ctx, path, fn)
192 }
193
194 func (c *cacheWrapper) URL(ctx context.Context, path string, options *SignedURLOptions) (string, error) {
195
196 return c.src.URL(ctx, path, options)
197 }
198
View as plain text