package cache import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "time" "git.nakama.town/fmartingr/gotoolkit/model" ) var _ model.Cache = (*FileCache)(nil) type FileCache struct { path string } func (c *FileCache) Get(key string) (result any, err error) { path := c.getPathForItem(key) if _, err := os.Stat(path + ".metadata"); os.IsNotExist(err) { return result, model.ErrCacheKeyDontExist } metadata, err := c.getMetadata(key) if err != nil { return result, fmt.Errorf("error getting metadata: %s", err) } if metadata.TTL != nil && metadata.TTL.Before(time.Now()) { if err := c.Delete(key); err != nil { return result, fmt.Errorf("error deleting cache key: %s", err) } return result, model.ErrCacheKeyDontExist } contents, err := os.ReadFile(path) if err != nil { if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrPermission) { return result, model.ErrCacheKeyDontExist } return } return contents, nil } func (c *FileCache) Set(key string, value any, opts ...model.CacheOption) error { path := c.getPathForItem(key) metadata := model.CacheItem{ Key: key, } for _, opt := range opts { opt(&metadata) } if err := c.setMetadata(key, metadata); err != nil { return fmt.Errorf("error setting metadata: %s", err) } if err := os.WriteFile(path, value.([]byte), 0766); err != nil { return fmt.Errorf("error writting cache file: %s", err) } return nil } func (c *FileCache) Delete(key string) error { path := c.getPathForItem(key) if err := os.Remove(path); err != nil { return fmt.Errorf("error deleting cache file: %s", err) } if err := os.Remove(path + ".metadata"); err != nil { return fmt.Errorf("error deleting cache metadata: %s", err) } return nil } func (c *FileCache) getPathForItem(key string) string { return filepath.Join(c.path, key) } func (c *FileCache) getMetadata(key string) (model.CacheItem, error) { path := c.getPathForItem(key) + ".metadata" contents, err := os.ReadFile(path) if err != nil { return model.CacheItem{}, fmt.Errorf("error reading cache file: %s", err) } var metadata model.CacheItem if err := json.Unmarshal(contents, &metadata); err != nil { return model.CacheItem{}, fmt.Errorf("error unmarshalling cache file: %s", err) } return metadata, nil } func (c *FileCache) setMetadata(key string, metadata model.CacheItem) error { path := c.getPathForItem(key) + ".metadata" contents, err := json.Marshal(metadata) if err != nil { return fmt.Errorf("error marshalling cache file: %s", err) } return os.WriteFile(path, contents, 0644) } func NewFileCache(folderName string) (*FileCache, error) { userCacheDir, err := os.UserCacheDir() if err != nil { return nil, fmt.Errorf("Could not retrieve user cache directory: %w", err) } path := filepath.Join(userCacheDir, folderName) if err := os.MkdirAll(path, 0755); err != nil { return nil, fmt.Errorf("couldn't create cache directory: %w", err) } return &FileCache{ path: path, }, nil }