package cache import ( "encoding/json" "errors" "fmt" "io" "os" "path/filepath" "strings" "time" ) type Cache[T any] struct { Path string } // New Cache where default path is os.TempDir() func New[T any](path string) *Cache[T] { if path == "" { path = os.TempDir() } return &Cache[T]{path} } var ( ErrorUnableToMarshalJSON = errors.New("unable to marshal as JSON") ErrorUnableToEnsureCacheDir = errors.New("unable to ensure cache-dir is present") ErrorUnableToWriteCache = errors.New("unable to write data to cache file") ErrorUnableToOpenCacheFile = errors.New("unable to open cache") ErrorUnableToReadCacheFile = errors.New("unable to read cache") ErrorUnableToUnmarshalCacheFile = errors.New("unable to unmarshal JSON from cache-file") ErrorUnableToReadCachePath = errors.New("unable to read from cache path") ) // Add data as JSON-file into cache-dir, returns nil if success func (c *Cache[T]) Add(name string, data any) error { j, err := json.MarshalIndent(data, "", " ") if err != nil { return ErrorUnableToMarshalJSON } err = os.MkdirAll(c.Path, 0646) if err != nil { return ErrorUnableToEnsureCacheDir } filePath := filepath.Join(c.Path, filename(name)) d := []byte(j) err = os.WriteFile(filePath, d, 0646) if err != nil { return ErrorUnableToWriteCache } return nil } // Get data from JSON-file from cache-dir, returns nil if no result func (c *Cache[T]) Get(name string) (*T, error) { filePath := filepath.Join(c.Path, filename(name)) if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) { return nil, nil } rawFile, err := os.Open(filePath) if err != nil { return nil, ErrorUnableToOpenCacheFile } var result T bytes, err := io.ReadAll(rawFile) if err != nil { return nil, ErrorUnableToReadCacheFile } err = json.Unmarshal(bytes, &result) if err != nil { return nil, ErrorUnableToUnmarshalCacheFile } return &result, nil } type Saved[T any] struct { Filename string Item T } func (c *Cache[T]) List() ([]Saved[T], error) { var result []Saved[T] filenames, err := list(c.Path) if err != nil { return nil, ErrorUnableToReadCachePath } if len(filenames) == 0 { return result, nil } for _, f := range filenames { cached, err := c.Get(f) if err != nil { return nil, err } result = append(result, Saved[T]{Filename: f, Item: *(cached)}) } return result, nil } func list(path string) ([]string, error) { dir, err := os.ReadDir(path) if err != nil { return nil, ErrorUnableToReadCachePath } var filenames []string for _, item := range dir { if !item.IsDir() && strings.Contains(item.Name(), prefix) { tmp := strings.ReplaceAll(strings.ReplaceAll(item.Name(), ".json", ""), prefix, "") filenames = append(filenames, tmp) } } return filenames, nil } func (c *Cache[T]) Clear() error { files, err := list(c.Path) if err != nil { return err } for _, f := range files { filePath := filepath.Join(c.Path, filename(f)) err = os.Remove(filePath) if err != nil { return err } } return nil } var prefix = "yr-" func filename(name string) string { return fmt.Sprintf("%s%s.json", prefix, name) } // Returns if t is expired func IsExpired(expire time.Time, now *time.Time) bool { if now == nil { tmp := time.Now() now = &tmp } return now.After(expire) }