...

Source file src/github.com/Shopify/go-storage/cache_wrapper.go

Documentation: github.com/Shopify/go-storage

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"time"
     7  )
     8  
     9  type CacheOptions struct {
    10  	// MaxAge is the maximum time allowed since the underlying File's ModTime
    11  	// This means that if the cache is older than MaxAge, the Cache will fetch from the src again.
    12  	// If the expired File is still present on the src (i.e. not updated), it will be ignored.
    13  	MaxAge time.Duration
    14  
    15  	// DefaultExpired makes the cache treat a File as expired if its CreationTime cannot be checked.
    16  	// By default, it is false, which means the cache will treat zero-CreationTime files as valid.
    17  	// Only useful if MaxAge is set.
    18  	DefaultExpired bool
    19  
    20  	// NoData disables caching of the contents of the entries, it only stores the metadata.
    21  	NoData bool
    22  }
    23  
    24  // NewCacheWrapper creates an FS implementation which caches files opened from src into cache.
    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  		// No expiration behavior
    81  		return false
    82  	}
    83  
    84  	creationTime := file.CreationTime
    85  	if creationTime.IsZero() {
    86  		creationTime = file.ModTime // Fallback to 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  		// Override the ReadCloser to actually fetch from the src
   102  		// If Read is not called, it still allows to read the attributes
   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  // Open implements FS.
   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() // The cache requires the CreationTime, so the original value is overwritten
   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  // Attributes implements FS.
   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  // Delete implements FS.
   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  // Create implements FS.
   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  // Walk implements FS.
   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  	// Pass-through
   196  	return c.src.URL(ctx, path, options)
   197  }
   198  

View as plain text