package store import ( "context" "path" "strings" "time" "github.com/ReneKroon/ttlcache/v2" ) var ( cachettl = 0 // these need to be global instances for now as the `Service` (and therefore the `Store`) are instantiated twice (for grpc and http) // therefore caches need to cover both instances dircache = initCache(cachettl) filescache = initCache(cachettl) ) // CachedMDC is cache for the metadataclient type CachedMDC struct { next MetadataClient files *ttlcache.Cache dirs *ttlcache.Cache } // SimpleDownload caches the answer from SimpleDownload or returns the cached one func (c *CachedMDC) SimpleDownload(ctx context.Context, id string) ([]byte, error) { if b, err := c.files.Get(id); err == nil { return b.([]byte), nil } b, err := c.next.SimpleDownload(ctx, id) if err != nil { return nil, err } _ = c.files.Set(id, b) return b, nil } // SimpleUpload caches the answer from SimpleUpload and invalidates the cache func (c *CachedMDC) SimpleUpload(ctx context.Context, id string, content []byte) error { b, err := c.files.Get(id) if err == nil && string(b.([]byte)) == string(content) { // no need to bug mdc return nil } err = c.next.SimpleUpload(ctx, id, content) if err != nil { return err } // invalidate caches _ = c.dirs.Remove(path.Dir(id)) _ = c.files.Set(id, content) return nil } // Delete invalidates the cache when operation was successful func (c *CachedMDC) Delete(ctx context.Context, id string) error { if err := c.next.Delete(ctx, id); err != nil { return err } // invalidate caches _ = removePrefix(c.files, id) _ = removePrefix(c.dirs, id) return nil } // ReadDir caches the response from ReadDir or returnes the cached one func (c *CachedMDC) ReadDir(ctx context.Context, id string) ([]string, error) { i, err := c.dirs.Get(id) if err == nil { return i.([]string), nil } s, err := c.next.ReadDir(ctx, id) if err != nil { return nil, err } return s, c.dirs.Set(id, s) } // MakeDirIfNotExist invalidates the cache func (c *CachedMDC) MakeDirIfNotExist(ctx context.Context, id string) error { err := c.next.MakeDirIfNotExist(ctx, id) if err != nil { return err } // invalidate caches _ = c.dirs.Remove(path.Dir(id)) return nil } // Init instantiates the caches func (c *CachedMDC) Init(ctx context.Context, id string) error { c.dirs = dircache c.files = filescache return c.next.Init(ctx, id) } func initCache(ttlSeconds int) *ttlcache.Cache { cache := ttlcache.NewCache() _ = cache.SetTTL(time.Duration(ttlSeconds) * time.Second) cache.SkipTTLExtensionOnHit(true) return cache } func removePrefix(cache *ttlcache.Cache, prefix string) error { for _, k := range cache.GetKeys() { if strings.HasPrefix(k, prefix) { if err := cache.Remove(k); err != nil { return err } } } return nil }