Files
cache/driver_file.go

238 lines
4.3 KiB
Go

package cache
import (
"crypto/md5"
"encoding/hex"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
// FileCache definition.
type FileCache struct {
BaseDriver
// caches in memory
MemoryCache
// cache directory path
cacheDir string
// DisableMemCache disable cache in memory
DisableMemCache bool
// FilePrefix cache file prefix
// FilePrefix string
// security key for generate cache file name.
securityKey string
}
// NewFileCache create a FileCache instance
func NewFileCache(dir string, pfxAndKey ...string) *FileCache {
if dir == "" { // empty, use system tmp dir
dir = os.TempDir()
}
c := &FileCache{
cacheDir: dir,
// init a memory cache.
MemoryCache: MemoryCache{caches: make(map[string]*Item)},
}
if ln := len(pfxAndKey); ln > 0 {
// c.prefix = pfxAndKey[0]
c.opt.Prefix = pfxAndKey[0]
if ln > 1 {
c.securityKey = pfxAndKey[1]
}
}
return c
}
// Has cache key. will check expire time
func (c *FileCache) Has(key string) bool {
return c.get(key) != nil
}
// Get value by key
func (c *FileCache) Get(key string) any {
c.lock.RLock()
defer c.lock.RUnlock()
return c.get(key)
}
func (c *FileCache) get(key string) any {
// read cache from memory
if val := c.MemoryCache.get(key); val != nil {
return val
}
// read cache from file
bs, err := ioutil.ReadFile(c.GetFilename(key))
if err != nil {
c.SetLastErr(err)
return nil
}
item := &Item{}
if err = c.UnmarshalTo(bs, item); err != nil {
c.SetLastErr(err)
return nil
}
// check expired
if item.Expired() {
c.SetLastErr(c.del(key))
return nil
}
c.caches[key] = item // save to memory.
return item.Val
}
// Set value by key
func (c *FileCache) Set(key string, val any, ttl time.Duration) (err error) {
c.lock.Lock()
defer c.lock.Unlock()
return c.set(key, val, ttl)
}
func (c *FileCache) set(key string, val any, ttl time.Duration) (err error) {
err = c.MemoryCache.set(key, val, ttl)
if err != nil {
return
}
// cache item data to file
bs, err := c.MustMarshal(c.caches[key])
if err != nil {
c.SetLastErr(err)
return
}
file := c.GetFilename(key)
dir := filepath.Dir(file)
if err = os.MkdirAll(dir, 0755); err != nil {
c.SetLastErr(err)
return
}
f, err := os.Create(file)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(bs)
return
}
// Del value by key
func (c *FileCache) Del(key string) error {
c.lock.Lock()
defer c.lock.Unlock()
return c.del(key)
}
func (c *FileCache) del(key string) error {
if err := c.MemoryCache.del(key); err != nil {
return err
}
file := c.GetFilename(key)
if fileExists(file) {
return os.Remove(file)
}
return nil
}
// GetMulti values by multi key
func (c *FileCache) GetMulti(keys []string) map[string]any {
c.lock.RLock()
defer c.lock.RUnlock()
data := make(map[string]any, len(keys))
for _, key := range keys {
data[key] = c.get(key)
}
return data
}
// SetMulti values by multi key
func (c *FileCache) SetMulti(values map[string]any, ttl time.Duration) (err error) {
c.lock.Lock()
defer c.lock.Unlock()
for key, val := range values {
if err = c.set(key, val, ttl); err != nil {
return
}
}
return
}
// DelMulti values by multi key
func (c *FileCache) DelMulti(keys []string) error {
c.lock.Lock()
defer c.lock.Unlock()
for _, key := range keys {
_ = c.del(key)
}
return nil
}
// Close cache
func (c *FileCache) Close() error {
return nil
}
// Clear caches and files
func (c *FileCache) Clear() error {
c.lock.Lock()
defer c.lock.Unlock()
for key := range c.caches {
if file := c.GetFilename(key); fileExists(file) {
err := os.Remove(file)
if err != nil {
return err
}
}
}
c.caches = nil
// clear cache files
return os.RemoveAll(c.cacheDir)
}
// GetFilename cache file name build
func (c *FileCache) GetFilename(key string) string {
h := md5.New()
if c.securityKey != "" {
h.Write([]byte(c.securityKey + key))
} else {
h.Write([]byte(key))
}
str := hex.EncodeToString(h.Sum(nil))
// return fmt.Sprintf("%s/%s/%s.data", c.cacheDir, str[0:6], c.prefix+str)
return strings.Join([]string{c.cacheDir, str[0:6], c.opt.Prefix + str + ".data"}, "/")
}
// fileExists reports whether the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}