2024-10-04 19:53:00 +00:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-10-17 19:16:39 +00:00
|
|
|
"strings"
|
2024-10-04 21:20:26 +00:00
|
|
|
"time"
|
2024-10-04 19:53:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Cache[T any] struct {
|
|
|
|
Path string
|
|
|
|
}
|
|
|
|
|
2024-10-05 21:36:31 +00:00
|
|
|
// New Cache where default path is os.TempDir()
|
2024-10-04 19:53:00 +00:00
|
|
|
func New[T any](path string) *Cache[T] {
|
|
|
|
if path == "" {
|
|
|
|
path = os.TempDir()
|
|
|
|
}
|
|
|
|
return &Cache[T]{path}
|
|
|
|
}
|
|
|
|
|
2024-11-05 17:49:00 +00:00
|
|
|
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")
|
|
|
|
)
|
|
|
|
|
2024-10-04 19:53:00 +00:00
|
|
|
// 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 {
|
2024-11-05 17:49:00 +00:00
|
|
|
return ErrorUnableToMarshalJSON
|
2024-10-04 19:53:00 +00:00
|
|
|
}
|
|
|
|
err = os.MkdirAll(c.Path, 0646)
|
|
|
|
if err != nil {
|
2024-11-05 17:49:00 +00:00
|
|
|
return ErrorUnableToEnsureCacheDir
|
2024-10-04 19:53:00 +00:00
|
|
|
}
|
|
|
|
|
2024-10-17 19:16:39 +00:00
|
|
|
filePath := filepath.Join(c.Path, filename(name))
|
2024-10-04 19:53:00 +00:00
|
|
|
d := []byte(j)
|
|
|
|
err = os.WriteFile(filePath, d, 0646)
|
|
|
|
if err != nil {
|
2024-11-05 17:49:00 +00:00
|
|
|
return ErrorUnableToWriteCache
|
2024-10-04 19:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get data from JSON-file from cache-dir, returns nil if no result
|
|
|
|
func (c *Cache[T]) Get(name string) (*T, error) {
|
2024-10-17 19:16:39 +00:00
|
|
|
filePath := filepath.Join(c.Path, filename(name))
|
2024-10-04 19:53:00 +00:00
|
|
|
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
rawFile, err := os.Open(filePath)
|
|
|
|
if err != nil {
|
2024-11-05 17:49:00 +00:00
|
|
|
return nil, ErrorUnableToOpenCacheFile
|
2024-10-04 19:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var result T
|
|
|
|
|
|
|
|
bytes, err := io.ReadAll(rawFile)
|
|
|
|
if err != nil {
|
2024-11-05 17:49:00 +00:00
|
|
|
return nil, ErrorUnableToReadCacheFile
|
2024-10-04 19:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(bytes, &result)
|
|
|
|
if err != nil {
|
2024-11-05 17:49:00 +00:00
|
|
|
return nil, ErrorUnableToUnmarshalCacheFile
|
2024-10-04 19:53:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &result, nil
|
|
|
|
}
|
2024-10-04 21:20:26 +00:00
|
|
|
|
2024-10-17 19:16:39 +00:00
|
|
|
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 {
|
2024-11-05 17:49:00 +00:00
|
|
|
return nil, ErrorUnableToReadCachePath
|
2024-10-17 19:16:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2024-11-05 17:49:00 +00:00
|
|
|
return nil, ErrorUnableToReadCachePath
|
2024-10-17 19:16:39 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-10-04 21:20:26 +00:00
|
|
|
// Returns if t is expired
|
|
|
|
func IsExpired(expire time.Time, now *time.Time) bool {
|
|
|
|
if now == nil {
|
|
|
|
tmp := time.Now()
|
|
|
|
now = &tmp
|
|
|
|
}
|
|
|
|
|
2024-11-06 15:01:40 +00:00
|
|
|
return now.After(expire)
|
2024-10-04 21:20:26 +00:00
|
|
|
}
|