1// Copyright (C) 2019 Storj Labs, Inc. 2// See LICENSE for copying information. 3 4package trust 5 6import ( 7 "context" 8 "encoding/json" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 13 "github.com/zeebo/errs" 14 15 "storj.io/common/fpath" 16) 17 18// Cache caches source information about trusted satellites. 19type Cache struct { 20 path string 21 data *CacheData 22} 23 24// LoadCache loads a cache from a file on disk. If the file is not present, the 25// cache is still loaded. If the file cannot be read for any other reason, the 26// function will return an error. LoadCache ensures the containing directory 27// exists. 28func LoadCache(path string) (*Cache, error) { 29 if path == "" { 30 return nil, Error.New("cache path cannot be empty") 31 } 32 if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil { 33 return nil, Error.New("unable to make cache parent directory: %w", err) 34 } 35 36 data, err := LoadCacheData(path) 37 switch { 38 case err == nil: 39 case errs.IsFunc(err, os.IsNotExist): 40 data = NewCacheData() 41 default: 42 return nil, err 43 } 44 45 return &Cache{ 46 path: path, 47 data: data, 48 }, nil 49} 50 51// Path returns the path on disk to the file containing the cache. 52func (cache *Cache) Path() string { 53 return cache.path 54} 55 56// Lookup takes a cache key and returns entries associated with that key. If 57// the key is unset in the cache, false is returned for ok. Otherwise the 58// entries are returned with ok returned as true. 59func (cache *Cache) Lookup(key string) (entries []Entry, ok bool) { 60 entries, ok = cache.data.Entries[key] 61 return entries, ok 62} 63 64// Set sets the entries in the cache for the provided key. 65func (cache *Cache) Set(key string, entries []Entry) { 66 cache.data.Entries[key] = entries 67} 68 69// Save persists the cache to disk. 70func (cache *Cache) Save(ctx context.Context) (err error) { 71 defer mon.Task()(&ctx)(&err) 72 return SaveCacheData(cache.path, cache.data) 73} 74 75// CacheData represents the data stored in the cache. 76type CacheData struct { 77 Entries map[string][]Entry `json:"entries"` 78} 79 80// NewCacheData returns an new CacheData. 81func NewCacheData() *CacheData { 82 return &CacheData{ 83 Entries: make(map[string][]Entry), 84 } 85} 86 87// LoadCacheData loads the cache data from the given path. 88func LoadCacheData(path string) (*CacheData, error) { 89 dataBytes, err := ioutil.ReadFile(path) 90 if err != nil { 91 return nil, Error.Wrap(err) 92 } 93 94 data := NewCacheData() 95 if err := json.Unmarshal(dataBytes, data); err != nil { 96 return nil, Error.New("malformed cache: %w", err) 97 } 98 // Ensure the entries map is always non-nil on load 99 if data.Entries == nil { 100 data.Entries = map[string][]Entry{} 101 } 102 return data, nil 103} 104 105// SaveCacheData persists the cache data to the given path. 106func SaveCacheData(path string, data *CacheData) error { 107 // Ensure the entries map is always non-nil on save 108 if data.Entries == nil { 109 data.Entries = map[string][]Entry{} 110 } 111 dataBytes, err := json.MarshalIndent(data, "", " ") 112 if err != nil { 113 return Error.Wrap(err) 114 } 115 return fpath.AtomicWriteFile(path, dataBytes, 0644) 116} 117