yr/pkg/cache/cache.go

160 lines
3.3 KiB
Go
Raw Normal View History

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)
}